diff options
Diffstat (limited to 'drivers/media/platform')
57 files changed, 5705 insertions, 1168 deletions
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index f6bed1971..dc75694ac 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -1,6 +1,6 @@ # # Platform drivers -# All drivers here are currently for webcam support +# Most drivers here are currently for webcam support menuconfig V4L_PLATFORM_DRIVERS bool "V4L platform devices" @@ -86,7 +86,7 @@ config VIDEO_M32R_AR_M64278 config VIDEO_OMAP3 tristate "OMAP 3 Camera support" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3 - depends on HAS_DMA + depends on HAS_DMA && OF depends on OMAP_IOMMU select ARM_DMA_USE_IOMMU select VIDEOBUF2_DMA_CONTIG @@ -231,6 +231,18 @@ config VIDEO_SH_VEU Support for the Video Engine Unit (VEU) on SuperH and SH-Mobile SoCs. +config VIDEO_RENESAS_JPU + tristate "Renesas JPEG Processing Unit" + depends on VIDEO_DEV && VIDEO_V4L2 + depends on ARCH_SHMOBILE || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + ---help--- + This is a V4L2 driver for the Renesas JPEG Processing Unit. + + To compile this driver as a module, choose M here: the module + will be called rcar_jpu. + config VIDEO_RENESAS_VSP1 tristate "Renesas VSP1 Video Processing Engine" depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA @@ -280,3 +292,14 @@ config VIDEO_VIM2M This is a virtual test device for the memory-to-memory driver framework. endif #V4L_TEST_DRIVERS + +menuconfig DVB_PLATFORM_DRIVERS + bool "DVB platform devices" + depends on MEDIA_DIGITAL_TV_SUPPORT + default n + ---help--- + Say Y here to enable support for platform-specific Digital TV drivers. + +if DVB_PLATFORM_DRIVERS +source "drivers/media/platform/sti/c8sectpfe/Kconfig" +endif #DVB_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 114f9aba1..efa0295af 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_G2D) += s5p-g2d/ obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC) += exynos-gsc/ obj-$(CONFIG_VIDEO_STI_BDISP) += sti/bdisp/ +obj-$(CONFIG_DVB_C8SECTPFE) += sti/c8sectpfe/ obj-$(CONFIG_BLACKFIN) += blackfin/ @@ -44,6 +45,7 @@ obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o obj-$(CONFIG_SOC_CAMERA) += soc_camera/ +obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ obj-y += omap/ diff --git a/drivers/media/platform/coda/Makefile b/drivers/media/platform/coda/Makefile index 834e504bf..9342ac57b 100644 --- a/drivers/media/platform/coda/Makefile +++ b/drivers/media/platform/coda/Makefile @@ -1,5 +1,5 @@ ccflags-y += -I$(src) -coda-objs := coda-common.o coda-bit.o coda-h264.o coda-jpeg.o +coda-objs := coda-common.o coda-bit.o coda-gdi.o coda-h264.o coda-jpeg.o obj-$(CONFIG_VIDEO_CODA) += coda.o diff --git a/drivers/media/platform/coda/coda-bit.c b/drivers/media/platform/coda/coda-bit.c index 109797bb8..fd7819d89 100644 --- a/drivers/media/platform/coda/coda-bit.c +++ b/drivers/media/platform/coda/coda-bit.c @@ -226,8 +226,12 @@ void coda_fill_bitstream(struct coda_ctx *ctx, bool streaming) { struct vb2_buffer *src_buf; struct coda_buffer_meta *meta; + unsigned long flags; u32 start; + if (ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG) + return; + while (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) > 0) { /* * Only queue a single JPEG into the bitstream buffer, except @@ -252,6 +256,13 @@ void coda_fill_bitstream(struct coda_ctx *ctx, bool streaming) continue; } + /* Dump empty buffers */ + if (!vb2_get_plane_payload(src_buf, 0)) { + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + continue; + } + /* Buffer start position */ start = ctx->bitstream_fifo.kfifo.in & ctx->bitstream_fifo.kfifo.mask; @@ -271,8 +282,13 @@ void coda_fill_bitstream(struct coda_ctx *ctx, bool streaming) meta->start = start; meta->end = ctx->bitstream_fifo.kfifo.in & ctx->bitstream_fifo.kfifo.mask; + spin_lock_irqsave(&ctx->buffer_meta_lock, + flags); list_add_tail(&meta->list, &ctx->buffer_meta_list); + ctx->num_metas++; + spin_unlock_irqrestore(&ctx->buffer_meta_lock, + flags); trace_coda_bit_queue(ctx, src_buf, meta); } @@ -331,7 +347,6 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, { struct coda_dev *dev = ctx->dev; int width, height; - dma_addr_t paddr; int ysize; int ret; int i; @@ -351,7 +366,10 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, size_t size; char *name; - size = ysize + ysize / 2; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + size = round_up(ysize, 4096) + ysize / 2; + else + size = ysize + ysize / 2; if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 && dev->devtype->product != CODA_DX6) size += ysize / 4; @@ -367,11 +385,23 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, /* Register frame buffers in the parameter buffer */ for (i = 0; i < ctx->num_internal_frames; i++) { - paddr = ctx->internal_frames[i].paddr; + u32 y, cb, cr; + /* Start addresses of Y, Cb, Cr planes */ - coda_parabuf_write(ctx, i * 3 + 0, paddr); - coda_parabuf_write(ctx, i * 3 + 1, paddr + ysize); - coda_parabuf_write(ctx, i * 3 + 2, paddr + ysize + ysize / 4); + y = ctx->internal_frames[i].paddr; + cb = y + ysize; + cr = y + ysize + ysize/4; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) { + cb = round_up(cb, 4096); + cr = 0; + /* Packed 20-bit MSB of base addresses */ + /* YYYYYCCC, CCyyyyyc, cccc.... */ + y = (y & 0xfffff000) | cb >> 20; + cb = (cb & 0x000ff000) << 12; + } + coda_parabuf_write(ctx, i * 3 + 0, y); + coda_parabuf_write(ctx, i * 3 + 1, cb); + coda_parabuf_write(ctx, i * 3 + 2, cr); /* mvcol buffer for h.264 */ if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 && @@ -384,7 +414,7 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, /* mvcol buffer for mpeg4 */ if ((dev->devtype->product != CODA_DX6) && (ctx->codec->src_fourcc == V4L2_PIX_FMT_MPEG4)) - coda_parabuf_write(ctx, 97, ctx->internal_frames[i].paddr + + coda_parabuf_write(ctx, 97, ctx->internal_frames[0].paddr + ysize + ysize/4 + ysize/4); return 0; @@ -712,6 +742,32 @@ err_clk_per: return ret; } +static void coda9_set_frame_cache(struct coda_ctx *ctx, u32 fourcc) +{ + u32 cache_size, cache_config; + + if (ctx->tiled_map_type == GDI_LINEAR_FRAME_MAP) { + /* Luma 2x0 page, 2x6 cache, chroma 2x0 page, 2x4 cache size */ + cache_size = 0x20262024; + cache_config = 2 << CODA9_CACHE_PAGEMERGE_OFFSET; + } else { + /* Luma 0x2 page, 4x4 cache, chroma 0x2 page, 4x3 cache size */ + cache_size = 0x02440243; + cache_config = 1 << CODA9_CACHE_PAGEMERGE_OFFSET; + } + coda_write(ctx->dev, cache_size, CODA9_CMD_SET_FRAME_CACHE_SIZE); + if (fourcc == V4L2_PIX_FMT_NV12) { + cache_config |= 32 << CODA9_CACHE_LUMA_BUFFER_SIZE_OFFSET | + 16 << CODA9_CACHE_CR_BUFFER_SIZE_OFFSET | + 0 << CODA9_CACHE_CB_BUFFER_SIZE_OFFSET; + } else { + cache_config |= 32 << CODA9_CACHE_LUMA_BUFFER_SIZE_OFFSET | + 8 << CODA9_CACHE_CR_BUFFER_SIZE_OFFSET | + 8 << CODA9_CACHE_CB_BUFFER_SIZE_OFFSET; + } + coda_write(ctx->dev, cache_config, CODA9_CMD_SET_FRAME_CACHE_CONFIG); +} + /* * Encoder context operations */ @@ -789,9 +845,12 @@ static int coda_start_encoding(struct coda_ctx *ctx) break; } - ctx->frame_mem_ctrl &= ~CODA_FRAME_CHROMA_INTERLEAVE; + ctx->frame_mem_ctrl &= ~(CODA_FRAME_CHROMA_INTERLEAVE | (0x3 << 9) | + CODA9_FRAME_TILED2LINEAR); if (q_data_src->fourcc == V4L2_PIX_FMT_NV12) ctx->frame_mem_ctrl |= CODA_FRAME_CHROMA_INTERLEAVE; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + ctx->frame_mem_ctrl |= (0x3 << 9) | CODA9_FRAME_TILED2LINEAR; coda_write(dev, ctx->frame_mem_ctrl, CODA_REG_BIT_FRAME_MEM_CTRL); if (dev->devtype->product == CODA_DX6) { @@ -913,6 +972,9 @@ static int coda_start_encoding(struct coda_ctx *ctx) value = (ctx->params.bitrate & CODA_RATECONTROL_BITRATE_MASK) << CODA_RATECONTROL_BITRATE_OFFSET; value |= 1 & CODA_RATECONTROL_ENABLE_MASK; + value |= (ctx->params.vbv_delay & + CODA_RATECONTROL_INITIALDELAY_MASK) + << CODA_RATECONTROL_INITIALDELAY_OFFSET; if (dev->devtype->product == CODA_960) value |= BIT(31); /* disable autoskip */ } else { @@ -920,7 +982,7 @@ static int coda_start_encoding(struct coda_ctx *ctx) } coda_write(dev, value, CODA_CMD_ENC_SEQ_RC_PARA); - coda_write(dev, 0, CODA_CMD_ENC_SEQ_RC_BUF_SIZE); + coda_write(dev, ctx->params.vbv_size, CODA_CMD_ENC_SEQ_RC_BUF_SIZE); coda_write(dev, ctx->params.intra_refresh, CODA_CMD_ENC_SEQ_INTRA_REFRESH); @@ -996,6 +1058,7 @@ static int coda_start_encoding(struct coda_ctx *ctx) ret = -EFAULT; goto out; } + ctx->initialized = 1; if (dst_fourcc != V4L2_PIX_FMT_JPEG) { if (dev->devtype->product == CODA_960) @@ -1036,6 +1099,8 @@ static int coda_start_encoding(struct coda_ctx *ctx) coda_write(dev, ctx->iram_info.buf_btp_use, CODA9_CMD_SET_FRAME_AXI_BTP_ADDR); + coda9_set_frame_cache(ctx, q_data_src->fourcc); + /* FIXME */ coda_write(dev, ctx->internal_frames[2].paddr, CODA9_CMD_SET_FRAME_SUBSAMP_A); @@ -1326,6 +1391,9 @@ static void coda_seq_end_work(struct work_struct *work) mutex_lock(&ctx->buffer_mutex); mutex_lock(&dev->coda_mutex); + if (ctx->initialized == 0) + goto out; + v4l2_dbg(1, coda_debug, &dev->v4l2_dev, "%d: %s: sent command 'SEQ_END' to coda\n", ctx->idx, __func__); @@ -1334,11 +1402,22 @@ static void coda_seq_end_work(struct work_struct *work) "CODA_COMMAND_SEQ_END failed\n"); } + /* + * FIXME: Sometimes h.264 encoding fails with 8-byte sequences missing + * from the output stream after the h.264 decoder has run. Resetting the + * hardware after the decoder has finished seems to help. + */ + if (dev->devtype->product == CODA_960) + coda_hw_reset(ctx); + kfifo_init(&ctx->bitstream_fifo, ctx->bitstream.vaddr, ctx->bitstream.size); coda_free_framebuffers(ctx); + ctx->initialized = 0; + +out: mutex_unlock(&dev->coda_mutex); mutex_unlock(&ctx->buffer_mutex); } @@ -1448,9 +1527,12 @@ static int __coda_start_decoding(struct coda_ctx *ctx) /* Update coda bitstream read and write pointers from kfifo */ coda_kfifo_sync_to_device_full(ctx); - ctx->frame_mem_ctrl &= ~CODA_FRAME_CHROMA_INTERLEAVE; + ctx->frame_mem_ctrl &= ~(CODA_FRAME_CHROMA_INTERLEAVE | (0x3 << 9) | + CODA9_FRAME_TILED2LINEAR); if (dst_fourcc == V4L2_PIX_FMT_NV12) ctx->frame_mem_ctrl |= CODA_FRAME_CHROMA_INTERLEAVE; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + ctx->frame_mem_ctrl |= (0x3 << 9) | CODA9_FRAME_TILED2LINEAR; coda_write(dev, ctx->frame_mem_ctrl, CODA_REG_BIT_FRAME_MEM_CTRL); ctx->display_idx = -1; @@ -1496,6 +1578,7 @@ static int __coda_start_decoding(struct coda_ctx *ctx) coda_write(dev, 0, CODA_REG_BIT_BIT_STREAM_PARAM); return -ETIMEDOUT; } + ctx->initialized = 1; /* Update kfifo out pointer from coda bitstream read pointer */ coda_kfifo_sync_from_device(ctx); @@ -1578,30 +1661,13 @@ static int __coda_start_decoding(struct coda_ctx *ctx) CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); coda_write(dev, ctx->iram_info.buf_ovl_use, CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); - if (dev->devtype->product == CODA_960) + if (dev->devtype->product == CODA_960) { coda_write(dev, ctx->iram_info.buf_btp_use, CODA9_CMD_SET_FRAME_AXI_BTP_ADDR); - } - if (dev->devtype->product == CODA_960) { - int cbb_size, crb_size; - - coda_write(dev, -1, CODA9_CMD_SET_FRAME_DELAY); - /* Luma 2x0 page, 2x6 cache, chroma 2x0 page, 2x4 cache size */ - coda_write(dev, 0x20262024, CODA9_CMD_SET_FRAME_CACHE_SIZE); - - if (dst_fourcc == V4L2_PIX_FMT_NV12) { - cbb_size = 0; - crb_size = 16; - } else { - cbb_size = 8; - crb_size = 8; + coda_write(dev, -1, CODA9_CMD_SET_FRAME_DELAY); + coda9_set_frame_cache(ctx, dst_fourcc); } - coda_write(dev, 2 << CODA9_CACHE_PAGEMERGE_OFFSET | - 32 << CODA9_CACHE_LUMA_BUFFER_SIZE_OFFSET | - cbb_size << CODA9_CACHE_CB_BUFFER_SIZE_OFFSET | - crb_size << CODA9_CACHE_CR_BUFFER_SIZE_OFFSET, - CODA9_CMD_SET_FRAME_CACHE_CONFIG); } if (src_fourcc == V4L2_PIX_FMT_H264) { @@ -1654,6 +1720,7 @@ static int coda_prepare_decode(struct coda_ctx *ctx) struct coda_dev *dev = ctx->dev; struct coda_q_data *q_data_dst; struct coda_buffer_meta *meta; + unsigned long flags; u32 reg_addr, reg_stride; dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); @@ -1732,6 +1799,7 @@ static int coda_prepare_decode(struct coda_ctx *ctx) coda_write(dev, ctx->iram_info.axi_sram_use, CODA7_REG_BIT_AXI_SRAM_USE); + spin_lock_irqsave(&ctx->buffer_meta_lock, flags); meta = list_first_entry_or_null(&ctx->buffer_meta_list, struct coda_buffer_meta, list); @@ -1751,6 +1819,7 @@ static int coda_prepare_decode(struct coda_ctx *ctx) kfifo_in(&ctx->bitstream_fifo, buf, pad); } } + spin_unlock_irqrestore(&ctx->buffer_meta_lock, flags); coda_kfifo_sync_to_device_full(ctx); @@ -1772,6 +1841,7 @@ static void coda_finish_decode(struct coda_ctx *ctx) struct vb2_buffer *dst_buf; struct coda_buffer_meta *meta; unsigned long payload; + unsigned long flags; int width, height; int decoded_idx; int display_idx; @@ -1897,12 +1967,21 @@ static void coda_finish_decode(struct coda_ctx *ctx) } else { val = coda_read(dev, CODA_RET_DEC_PIC_FRAME_NUM) - 1; val -= ctx->sequence_offset; - mutex_lock(&ctx->bitstream_mutex); + spin_lock_irqsave(&ctx->buffer_meta_lock, flags); if (!list_empty(&ctx->buffer_meta_list)) { meta = list_first_entry(&ctx->buffer_meta_list, struct coda_buffer_meta, list); list_del(&meta->list); - if (val != (meta->sequence & 0xffff)) { + ctx->num_metas--; + spin_unlock_irqrestore(&ctx->buffer_meta_lock, flags); + /* + * Clamp counters to 16 bits for comparison, as the HW + * counter rolls over at this point for h.264. This + * may be different for other formats, but using 16 bits + * should be enough to detect most errors and saves us + * from doing different things based on the format. + */ + if ((val & 0xffff) != (meta->sequence & 0xffff)) { v4l2_err(&dev->v4l2_dev, "sequence number mismatch (%d(%d) != %d)\n", val, ctx->sequence_offset, @@ -1911,13 +1990,13 @@ static void coda_finish_decode(struct coda_ctx *ctx) ctx->frame_metas[decoded_idx] = *meta; kfree(meta); } else { + spin_unlock_irqrestore(&ctx->buffer_meta_lock, flags); v4l2_err(&dev->v4l2_dev, "empty timestamp list!\n"); memset(&ctx->frame_metas[decoded_idx], 0, sizeof(struct coda_buffer_meta)); ctx->frame_metas[decoded_idx].sequence = val; ctx->sequence_offset++; } - mutex_unlock(&ctx->bitstream_mutex); trace_coda_dec_pic_done(ctx, &ctx->frame_metas[decoded_idx]); @@ -1960,7 +2039,7 @@ static void coda_finish_decode(struct coda_ctx *ctx) dst_buf->v4l2_buf.timecode = meta->timecode; dst_buf->v4l2_buf.timestamp = meta->timestamp; - trace_coda_dec_rot_done(ctx, meta, dst_buf); + trace_coda_dec_rot_done(ctx, dst_buf, meta); switch (q_data_dst->fourcc) { case V4L2_PIX_FMT_YUV420: diff --git a/drivers/media/platform/coda/coda-common.c b/drivers/media/platform/coda/coda-common.c index 6e41b0ce8..c48ef89f9 100644 --- a/drivers/media/platform/coda/coda-common.c +++ b/drivers/media/platform/coda/coda-common.c @@ -15,6 +15,7 @@ #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/firmware.h> +#include <linux/gcd.h> #include <linux/genalloc.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -61,10 +62,9 @@ int coda_debug; module_param(coda_debug, int, 0644); MODULE_PARM_DESC(coda_debug, "Debug level (0-2)"); -struct coda_fmt { - char *name; - u32 fourcc; -}; +static int disable_tiling; +module_param(disable_tiling, int, 0644); +MODULE_PARM_DESC(disable_tiling, "Disable tiled frame buffers"); void coda_write(struct coda_dev *dev, u32 data, u32 reg) { @@ -90,17 +90,17 @@ void coda_write_base(struct coda_ctx *ctx, struct coda_q_data *q_data, u32 base_cb, base_cr; switch (q_data->fourcc) { - case V4L2_PIX_FMT_YVU420: - /* Switch Cb and Cr for YVU420 format */ - base_cr = base_y + q_data->bytesperline * q_data->height; - base_cb = base_cr + q_data->bytesperline * q_data->height / 4; - break; - case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUV420: default: base_cb = base_y + q_data->bytesperline * q_data->height; base_cr = base_cb + q_data->bytesperline * q_data->height / 4; break; + case V4L2_PIX_FMT_YVU420: + /* Switch Cb and Cr for YVU420 format */ + base_cr = base_y + q_data->bytesperline * q_data->height; + base_cb = base_cr + q_data->bytesperline * q_data->height / 4; + break; case V4L2_PIX_FMT_YUV422P: base_cb = base_y + q_data->bytesperline * q_data->height; base_cr = base_cb + q_data->bytesperline * q_data->height / 2; @@ -111,40 +111,6 @@ void coda_write_base(struct coda_ctx *ctx, struct coda_q_data *q_data, coda_write(ctx->dev, base_cr, reg_y + 8); } -/* - * Array of all formats supported by any version of Coda: - */ -static const struct coda_fmt coda_formats[] = { - { - .name = "YUV 4:2:0 Planar, YCbCr", - .fourcc = V4L2_PIX_FMT_YUV420, - }, - { - .name = "YUV 4:2:0 Planar, YCrCb", - .fourcc = V4L2_PIX_FMT_YVU420, - }, - { - .name = "YUV 4:2:0 Partial interleaved Y/CbCr", - .fourcc = V4L2_PIX_FMT_NV12, - }, - { - .name = "YUV 4:2:2 Planar, YCbCr", - .fourcc = V4L2_PIX_FMT_YUV422P, - }, - { - .name = "H264 Encoded Stream", - .fourcc = V4L2_PIX_FMT_H264, - }, - { - .name = "MPEG4 Encoded Stream", - .fourcc = V4L2_PIX_FMT_MPEG4, - }, - { - .name = "JPEG Encoded Images", - .fourcc = V4L2_PIX_FMT_JPEG, - }, -}; - #define CODA_CODEC(mode, src_fourcc, dst_fourcc, max_w, max_h) \ { mode, src_fourcc, dst_fourcc, max_w, max_h } @@ -190,9 +156,9 @@ static const struct coda_video_device coda_bit_encoder = { .type = CODA_INST_ENCODER, .ops = &coda_bit_encode_ops, .src_formats = { + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, - V4L2_PIX_FMT_NV12, }, .dst_formats = { V4L2_PIX_FMT_H264, @@ -205,9 +171,9 @@ static const struct coda_video_device coda_bit_jpeg_encoder = { .type = CODA_INST_ENCODER, .ops = &coda_bit_encode_ops, .src_formats = { + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, - V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV422P, }, .dst_formats = { @@ -224,9 +190,9 @@ static const struct coda_video_device coda_bit_decoder = { V4L2_PIX_FMT_MPEG4, }, .dst_formats = { + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, - V4L2_PIX_FMT_NV12, }, }; @@ -238,9 +204,9 @@ static const struct coda_video_device coda_bit_jpeg_decoder = { V4L2_PIX_FMT_JPEG, }, .dst_formats = { + V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, - V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV422P, }, }; @@ -261,38 +227,21 @@ static const struct coda_video_device *coda9_video_devices[] = { &coda_bit_decoder, }; -static bool coda_format_is_yuv(u32 fourcc) +/* + * Normalize all supported YUV 4:2:0 formats to the value used in the codec + * tables. + */ +static u32 coda_format_normalize_yuv(u32 fourcc) { switch (fourcc) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_YUV422P: - return true; + return V4L2_PIX_FMT_YUV420; default: - return false; - } -} - -static const char *coda_format_name(u32 fourcc) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(coda_formats); i++) { - if (coda_formats[i].fourcc == fourcc) - return coda_formats[i].name; + return fourcc; } - - return NULL; -} - -/* - * Normalize all supported YUV 4:2:0 formats to the value used in the codec - * tables. - */ -static u32 coda_format_normalize_yuv(u32 fourcc) -{ - return coda_format_is_yuv(fourcc) ? V4L2_PIX_FMT_YUV420 : fourcc; } static const struct coda_codec *coda_find_codec(struct coda_dev *dev, @@ -396,7 +345,6 @@ static int coda_enum_fmt(struct file *file, void *priv, struct video_device *vdev = video_devdata(file); const struct coda_video_device *cvd = to_coda_video_device(vdev); const u32 *formats; - const char *name; if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) formats = cvd->src_formats; @@ -408,11 +356,7 @@ static int coda_enum_fmt(struct file *file, void *priv, if (f->index >= CODA_MAX_FORMATS || formats[f->index] == 0) return -EINVAL; - name = coda_format_name(formats[f->index]); - strlcpy(f->description, name, sizeof(f->description)); f->pixelformat = formats[f->index]; - if (!coda_format_is_yuv(formats[f->index])) - f->flags |= V4L2_FMT_FLAG_COMPRESSED; return 0; } @@ -504,9 +448,9 @@ static int coda_try_fmt(struct coda_ctx *ctx, const struct coda_codec *codec, S_ALIGN); switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_NV12: /* * Frame stride must be at least multiple of 8, * but multiple of 16 for h.264 or JPEG 4:2:x @@ -645,6 +589,22 @@ static int coda_s_fmt(struct coda_ctx *ctx, struct v4l2_format *f) q_data->rect.width = f->fmt.pix.width; q_data->rect.height = f->fmt.pix.height; + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_NV12: + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ctx->tiled_map_type = GDI_TILED_FRAME_MB_RASTER_MAP; + if (!disable_tiling) + break; + } + /* else fall through */ + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + ctx->tiled_map_type = GDI_LINEAR_FRAME_MAP; + break; + default: + break; + } + v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "Setting format for type %d, wxh: %dx%d, fmt: %d\n", f->type, q_data->width, q_data->height, q_data->fourcc); @@ -831,6 +791,104 @@ static int coda_decoder_cmd(struct file *file, void *fh, return 0; } +static int coda_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct v4l2_fract *tpf; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + tpf = &a->parm.output.timeperframe; + tpf->denominator = ctx->params.framerate & CODA_FRATE_RES_MASK; + tpf->numerator = 1 + (ctx->params.framerate >> + CODA_FRATE_DIV_OFFSET); + + return 0; +} + +/* + * Approximate timeperframe v4l2_fract with values that can be written + * into the 16-bit CODA_FRATE_DIV and CODA_FRATE_RES fields. + */ +static void coda_approximate_timeperframe(struct v4l2_fract *timeperframe) +{ + struct v4l2_fract s = *timeperframe; + struct v4l2_fract f0; + struct v4l2_fract f1 = { 1, 0 }; + struct v4l2_fract f2 = { 0, 1 }; + unsigned int i, div, s_denominator; + + /* Lower bound is 1/65535 */ + if (s.numerator == 0 || s.denominator / s.numerator > 65535) { + timeperframe->numerator = 1; + timeperframe->denominator = 65535; + return; + } + + /* Upper bound is 65536/1, map everything above to infinity */ + if (s.denominator == 0 || s.numerator / s.denominator > 65536) { + timeperframe->numerator = 1; + timeperframe->denominator = 0; + return; + } + + /* Reduce fraction to lowest terms */ + div = gcd(s.numerator, s.denominator); + if (div > 1) { + s.numerator /= div; + s.denominator /= div; + } + + if (s.numerator <= 65536 && s.denominator < 65536) { + *timeperframe = s; + return; + } + + /* Find successive convergents from continued fraction expansion */ + while (f2.numerator <= 65536 && f2.denominator < 65536) { + f0 = f1; + f1 = f2; + + /* Stop when f2 exactly equals timeperframe */ + if (s.numerator == 0) + break; + + i = s.denominator / s.numerator; + + f2.numerator = f0.numerator + i * f1.numerator; + f2.denominator = f0.denominator + i * f2.denominator; + + s_denominator = s.numerator; + s.numerator = s.denominator % s.numerator; + s.denominator = s_denominator; + } + + *timeperframe = f1; +} + +static uint32_t coda_timeperframe_to_frate(struct v4l2_fract *timeperframe) +{ + return ((timeperframe->numerator - 1) << CODA_FRATE_DIV_OFFSET) | + timeperframe->denominator; +} + +static int coda_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct v4l2_fract *tpf; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + tpf = &a->parm.output.timeperframe; + coda_approximate_timeperframe(tpf); + ctx->params.framerate = coda_timeperframe_to_frate(tpf); + + return 0; +} + static int coda_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -871,31 +929,13 @@ static const struct v4l2_ioctl_ops coda_ioctl_ops = { .vidioc_try_decoder_cmd = coda_try_decoder_cmd, .vidioc_decoder_cmd = coda_decoder_cmd, + .vidioc_g_parm = coda_g_parm, + .vidioc_s_parm = coda_s_parm, + .vidioc_subscribe_event = coda_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; -void coda_set_gdi_regs(struct coda_ctx *ctx) -{ - struct gdi_tiled_map *tiled_map = &ctx->tiled_map; - struct coda_dev *dev = ctx->dev; - int i; - - for (i = 0; i < 16; i++) - coda_write(dev, tiled_map->xy2ca_map[i], - CODA9_GDI_XY2_CAS_0 + 4 * i); - for (i = 0; i < 4; i++) - coda_write(dev, tiled_map->xy2ba_map[i], - CODA9_GDI_XY2_BA_0 + 4 * i); - for (i = 0; i < 16; i++) - coda_write(dev, tiled_map->xy2ra_map[i], - CODA9_GDI_XY2_RAS_0 + 4 * i); - coda_write(dev, tiled_map->xy2rbc_config, CODA9_GDI_XY2_RBC_CONFIG); - for (i = 0; i < 32; i++) - coda_write(dev, tiled_map->rbc2axi_map[i], - CODA9_GDI_RBC2_AXI_0 + 4 * i); -} - /* * Mem-to-mem operations. */ @@ -949,14 +989,14 @@ static void coda_pic_run_work(struct work_struct *work) static int coda_job_ready(void *m2m_priv) { struct coda_ctx *ctx = m2m_priv; + int src_bufs = v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx); /* * For both 'P' and 'key' frame cases 1 picture * and 1 frame are needed. In the decoder case, * the compressed frame can be in the bitstream. */ - if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) && - ctx->inst_type != CODA_INST_DECODER) { + if (!src_bufs && ctx->inst_type != CODA_INST_DECODER) { v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "not ready: not enough video buffers.\n"); return 0; @@ -969,27 +1009,17 @@ static int coda_job_ready(void *m2m_priv) } if (ctx->inst_type == CODA_INST_DECODER && ctx->use_bit) { - struct list_head *meta; - bool stream_end; - int num_metas; - int src_bufs; + bool stream_end = ctx->bit_stream_param & + CODA_BIT_STREAM_END_FLAG; + int num_metas = ctx->num_metas; - if (ctx->hold && !v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx)) { + if (ctx->hold && !src_bufs) { v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "%d: not ready: on hold for more buffers.\n", ctx->idx); return 0; } - stream_end = ctx->bit_stream_param & - CODA_BIT_STREAM_END_FLAG; - - num_metas = 0; - list_for_each(meta, &ctx->buffer_meta_list) - num_metas++; - - src_bufs = v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx); - if (!stream_end && (num_metas + src_bufs) < 2) { v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "%d: not ready: need 2 buffers available (%d, %d)\n", @@ -998,8 +1028,8 @@ static int coda_job_ready(void *m2m_priv) } - if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) && - !stream_end && (coda_get_bitstream_payload(ctx) < 512)) { + if (!src_bufs && !stream_end && + (coda_get_bitstream_payload(ctx) < 512)) { v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "%d: not ready: not enough bitstream data (%d).\n", ctx->idx, coda_get_bitstream_payload(ctx)); @@ -1015,6 +1045,7 @@ static int coda_job_ready(void *m2m_priv) v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "job ready\n"); + return 1; } @@ -1052,32 +1083,6 @@ static const struct v4l2_m2m_ops coda_m2m_ops = { .unlock = coda_unlock, }; -static void coda_set_tiled_map_type(struct coda_ctx *ctx, int tiled_map_type) -{ - struct gdi_tiled_map *tiled_map = &ctx->tiled_map; - int luma_map, chro_map, i; - - memset(tiled_map, 0, sizeof(*tiled_map)); - - luma_map = 64; - chro_map = 64; - tiled_map->map_type = tiled_map_type; - for (i = 0; i < 16; i++) - tiled_map->xy2ca_map[i] = luma_map << 8 | chro_map; - for (i = 0; i < 4; i++) - tiled_map->xy2ba_map[i] = luma_map << 8 | chro_map; - for (i = 0; i < 16; i++) - tiled_map->xy2ra_map[i] = luma_map << 8 | chro_map; - - if (tiled_map_type == GDI_LINEAR_FRAME_MAP) { - tiled_map->xy2rbc_config = 0; - } else { - dev_err(&ctx->dev->plat_dev->dev, "invalid map type: %d\n", - tiled_map_type); - return; - } -} - static void set_default_params(struct coda_ctx *ctx) { unsigned int max_w, max_h, usize, csize; @@ -1094,8 +1099,8 @@ static void set_default_params(struct coda_ctx *ctx) ctx->params.framerate = 30; /* Default formats for output and input queues */ - ctx->q_data[V4L2_M2M_SRC].fourcc = ctx->codec->src_fourcc; - ctx->q_data[V4L2_M2M_DST].fourcc = ctx->codec->dst_fourcc; + ctx->q_data[V4L2_M2M_SRC].fourcc = ctx->cvd->src_formats[0]; + ctx->q_data[V4L2_M2M_DST].fourcc = ctx->cvd->dst_formats[0]; ctx->q_data[V4L2_M2M_SRC].width = max_w; ctx->q_data[V4L2_M2M_SRC].height = max_h; ctx->q_data[V4L2_M2M_DST].width = max_w; @@ -1116,8 +1121,11 @@ static void set_default_params(struct coda_ctx *ctx) ctx->q_data[V4L2_M2M_DST].rect.width = max_w; ctx->q_data[V4L2_M2M_DST].rect.height = max_h; - if (ctx->dev->devtype->product == CODA_960) - coda_set_tiled_map_type(ctx, GDI_LINEAR_FRAME_MAP); + /* + * Since the RBC2AXI logic only supports a single chroma plane, + * macroblock tiling only works for to NV12 pixel format. + */ + ctx->tiled_map_type = GDI_LINEAR_FRAME_MAP; } /* @@ -1244,9 +1252,7 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { - if (q_data_src->fourcc == V4L2_PIX_FMT_H264 || - (q_data_src->fourcc == V4L2_PIX_FMT_JPEG && - ctx->dev->devtype->product == CODA_7541)) { + if (ctx->inst_type == CODA_INST_DECODER && ctx->use_bit) { /* copy the buffers that were queued before streamon */ mutex_lock(&ctx->bitstream_mutex); coda_fill_bitstream(ctx, false); @@ -1315,7 +1321,6 @@ static int coda_start_streaming(struct vb2_queue *q, unsigned int count) goto err; } - ctx->initialized = 1; return ret; err: @@ -1334,6 +1339,7 @@ static void coda_stop_streaming(struct vb2_queue *q) struct coda_ctx *ctx = vb2_get_drv_priv(q); struct coda_dev *dev = ctx->dev; struct vb2_buffer *buf; + unsigned long flags; bool stop; stop = ctx->streamon_out && ctx->streamon_cap; @@ -1368,20 +1374,23 @@ static void coda_stop_streaming(struct vb2_queue *q) queue_work(dev->workqueue, &ctx->seq_end_work); flush_work(&ctx->seq_end_work); } - mutex_lock(&ctx->bitstream_mutex); + spin_lock_irqsave(&ctx->buffer_meta_lock, flags); while (!list_empty(&ctx->buffer_meta_list)) { meta = list_first_entry(&ctx->buffer_meta_list, struct coda_buffer_meta, list); list_del(&meta->list); kfree(meta); } - mutex_unlock(&ctx->bitstream_mutex); + ctx->num_metas = 0; + spin_unlock_irqrestore(&ctx->buffer_meta_lock, flags); kfifo_init(&ctx->bitstream_fifo, ctx->bitstream.vaddr, ctx->bitstream.size); - ctx->initialized = 0; ctx->runcounter = 0; ctx->aborting = 0; } + + if (!ctx->streamon_out && !ctx->streamon_cap) + ctx->bit_stream_param &= ~CODA_BIT_STREAM_END_FLAG; } static const struct vb2_ops coda_qops = { @@ -1469,6 +1478,12 @@ static int coda_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_JPEG_RESTART_INTERVAL: ctx->params.jpeg_restart_interval = ctrl->val; break; + case V4L2_CID_MPEG_VIDEO_VBV_DELAY: + ctx->params.vbv_delay = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VBV_SIZE: + ctx->params.vbv_size = min(ctrl->val * 8192, 0x7fffffff); + break; default: v4l2_dbg(1, coda_debug, &ctx->dev->v4l2_dev, "Invalid control, id=%d, val=%d\n", @@ -1528,6 +1543,14 @@ static void coda_encode_ctrls(struct coda_ctx *ctx) v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB, 0, 1920 * 1088 / 256, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VBV_DELAY, 0, 0x7fff, 1, 0); + /* + * The maximum VBV size value is 0x7fffffff bits, + * one bit less than 262144 KiB + */ + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VBV_SIZE, 0, 262144, 1, 0); } static void coda_jpeg_encode_ctrls(struct coda_ctx *ctx) @@ -1726,6 +1749,7 @@ static int coda_open(struct file *file) mutex_init(&ctx->bitstream_mutex); mutex_init(&ctx->buffer_mutex); INIT_LIST_HEAD(&ctx->buffer_meta_list); + spin_lock_init(&ctx->buffer_meta_lock); coda_lock(ctx); list_add(&ctx->list, &dev->instances); @@ -1769,7 +1793,7 @@ static int coda_release(struct file *file) v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); /* In case the instance was not running, we still need to call SEQ_END */ - if (ctx->initialized && ctx->ops->seq_end_work) { + if (ctx->ops->seq_end_work) { queue_work(dev->workqueue, &ctx->seq_end_work); flush_work(&ctx->seq_end_work); } @@ -2157,7 +2181,7 @@ static int coda_probe(struct platform_device *pdev) /* Get IRAM pool from device tree or platform data */ pool = of_gen_pool_get(np, "iram", 0); if (!pool && pdata) - pool = gen_pool_get(pdata->iram_dev); + pool = gen_pool_get(pdata->iram_dev, NULL); if (!pool) { dev_err(&pdev->dev, "iram pool not available\n"); return -ENOMEM; diff --git a/drivers/media/platform/coda/coda-gdi.c b/drivers/media/platform/coda/coda-gdi.c new file mode 100644 index 000000000..aaa7afc68 --- /dev/null +++ b/drivers/media/platform/coda/coda-gdi.c @@ -0,0 +1,150 @@ +/* + * Coda multi-standard codec IP + * + * Copyright (C) 2014 Philipp Zabel, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/bitops.h> +#include "coda.h" + +#define XY2_INVERT BIT(7) +#define XY2_ZERO BIT(6) +#define XY2_TB_XOR BIT(5) +#define XY2_XYSEL BIT(4) +#define XY2_Y (1 << 4) +#define XY2_X (0 << 4) + +#define XY2(luma_sel, luma_bit, chroma_sel, chroma_bit) \ + (((XY2_##luma_sel) | (luma_bit)) << 8 | \ + (XY2_##chroma_sel) | (chroma_bit)) + +static const u16 xy2ca_zero_map[16] = { + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), +}; + +static const u16 xy2ca_tiled_map[16] = { + XY2(Y, 0, Y, 0), + XY2(Y, 1, Y, 1), + XY2(Y, 2, Y, 2), + XY2(Y, 3, X, 3), + XY2(X, 3, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), +}; + +/* + * RA[15:0], CA[15:8] are hardwired to contain the 24-bit macroblock + * start offset (macroblock size is 16x16 for luma, 16x8 for chroma). + * Bits CA[4:0] are set using XY2CA above. BA[3:0] seems to be unused. + */ + +#define RBC_CA (0 << 4) +#define RBC_BA (1 << 4) +#define RBC_RA (2 << 4) +#define RBC_ZERO (3 << 4) + +#define RBC(luma_sel, luma_bit, chroma_sel, chroma_bit) \ + (((RBC_##luma_sel) | (luma_bit)) << 6 | \ + (RBC_##chroma_sel) | (chroma_bit)) + +static const u16 rbc2axi_tiled_map[32] = { + RBC(ZERO, 0, ZERO, 0), + RBC(ZERO, 0, ZERO, 0), + RBC(ZERO, 0, ZERO, 0), + RBC(CA, 0, CA, 0), + RBC(CA, 1, CA, 1), + RBC(CA, 2, CA, 2), + RBC(CA, 3, CA, 3), + RBC(CA, 4, CA, 8), + RBC(CA, 8, CA, 9), + RBC(CA, 9, CA, 10), + RBC(CA, 10, CA, 11), + RBC(CA, 11, CA, 12), + RBC(CA, 12, CA, 13), + RBC(CA, 13, CA, 14), + RBC(CA, 14, CA, 15), + RBC(CA, 15, RA, 0), + RBC(RA, 0, RA, 1), + RBC(RA, 1, RA, 2), + RBC(RA, 2, RA, 3), + RBC(RA, 3, RA, 4), + RBC(RA, 4, RA, 5), + RBC(RA, 5, RA, 6), + RBC(RA, 6, RA, 7), + RBC(RA, 7, RA, 8), + RBC(RA, 8, RA, 9), + RBC(RA, 9, RA, 10), + RBC(RA, 10, RA, 11), + RBC(RA, 11, RA, 12), + RBC(RA, 12, RA, 13), + RBC(RA, 13, RA, 14), + RBC(RA, 14, RA, 15), + RBC(RA, 15, ZERO, 0), +}; + +void coda_set_gdi_regs(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + const u16 *xy2ca_map; + u32 xy2rbc_config; + int i; + + switch (ctx->tiled_map_type) { + case GDI_LINEAR_FRAME_MAP: + default: + xy2ca_map = xy2ca_zero_map; + xy2rbc_config = 0; + break; + case GDI_TILED_FRAME_MB_RASTER_MAP: + xy2ca_map = xy2ca_tiled_map; + xy2rbc_config = CODA9_XY2RBC_TILED_MAP | + CODA9_XY2RBC_CA_INC_HOR | + (16 - 1) << 12 | (8 - 1) << 4; + break; + } + + for (i = 0; i < 16; i++) + coda_write(dev, xy2ca_map[i], + CODA9_GDI_XY2_CAS_0 + 4 * i); + for (i = 0; i < 4; i++) + coda_write(dev, XY2(ZERO, 0, ZERO, 0), + CODA9_GDI_XY2_BA_0 + 4 * i); + for (i = 0; i < 16; i++) + coda_write(dev, XY2(ZERO, 0, ZERO, 0), + CODA9_GDI_XY2_RAS_0 + 4 * i); + coda_write(dev, xy2rbc_config, CODA9_GDI_XY2_RBC_CONFIG); + if (xy2rbc_config) { + for (i = 0; i < 32; i++) + coda_write(dev, rbc2axi_tiled_map[i], + CODA9_GDI_RBC2_AXI_0 + 4 * i); + } +} diff --git a/drivers/media/platform/coda/coda.h b/drivers/media/platform/coda/coda.h index 8e0af221b..59b2af9c7 100644 --- a/drivers/media/platform/coda/coda.h +++ b/drivers/media/platform/coda/coda.h @@ -128,6 +128,8 @@ struct coda_params { enum v4l2_mpeg_video_multi_slice_mode slice_mode; u32 framerate; u16 bitrate; + u16 vbv_delay; + u32 vbv_size; u32 slice_max_bits; u32 slice_max_mb; }; @@ -165,15 +167,8 @@ struct coda_iram_info { phys_addr_t next_paddr; }; -struct gdi_tiled_map { - int xy2ca_map[16]; - int xy2ba_map[16]; - int xy2ra_map[16]; - int rbc2axi_map[32]; - int xy2rbc_config; - int map_type; #define GDI_LINEAR_FRAME_MAP 0 -}; +#define GDI_TILED_FRAME_MB_RASTER_MAP 1 struct coda_ctx; @@ -227,12 +222,14 @@ struct coda_ctx { struct coda_buffer_meta frame_metas[CODA_MAX_FRAMEBUFFERS]; u32 frame_errors[CODA_MAX_FRAMEBUFFERS]; struct list_head buffer_meta_list; + spinlock_t buffer_meta_lock; + int num_metas; struct coda_aux_buf workbuf; int num_internal_frames; int idx; int reg_idx; struct coda_iram_info iram_info; - struct gdi_tiled_map tiled_map; + int tiled_map_type; u32 bit_stream_param; u32 frm_dis_flg; u32 frame_mem_ctrl; diff --git a/drivers/media/platform/coda/coda_regs.h b/drivers/media/platform/coda/coda_regs.h index 7d0262411..3490602fa 100644 --- a/drivers/media/platform/coda/coda_regs.h +++ b/drivers/media/platform/coda/coda_regs.h @@ -51,6 +51,7 @@ #define CODA7_STREAM_SEL_64BITS_ENDIAN (1 << 1) #define CODA_STREAM_ENDIAN_SELECT (1 << 0) #define CODA_REG_BIT_FRAME_MEM_CTRL 0x110 +#define CODA9_FRAME_TILED2LINEAR (1 << 11) #define CODA_FRAME_CHROMA_INTERLEAVE (1 << 2) #define CODA_IMAGE_ENDIAN_SELECT (1 << 0) #define CODA_REG_BIT_BIT_STREAM_PARAM 0x114 @@ -263,6 +264,10 @@ #define CODADX6_PICHEIGHT_MASK 0x3ff #define CODA7_PICHEIGHT_MASK 0xffff #define CODA_CMD_ENC_SEQ_SRC_F_RATE 0x194 +#define CODA_FRATE_RES_OFFSET 0 +#define CODA_FRATE_RES_MASK 0xffff +#define CODA_FRATE_DIV_OFFSET 16 +#define CODA_FRATE_DIV_MASK 0xffff #define CODA_CMD_ENC_SEQ_MP4_PARA 0x198 #define CODA_MP4PARAM_VERID_OFFSET 6 #define CODA_MP4PARAM_VERID_MASK 0x01 @@ -448,7 +453,12 @@ #define CODA9_GDI_XY2_RAS_F (CODA9_GDMA_BASE + 0x88c) #define CODA9_GDI_XY2_RBC_CONFIG (CODA9_GDMA_BASE + 0x890) +#define CODA9_XY2RBC_SEPARATE_MAP BIT(19) +#define CODA9_XY2RBC_TOP_BOT_SPLIT BIT(18) +#define CODA9_XY2RBC_TILED_MAP BIT(17) +#define CODA9_XY2RBC_CA_INC_HOR BIT(16) #define CODA9_GDI_RBC2_AXI_0 (CODA9_GDMA_BASE + 0x8a0) #define CODA9_GDI_RBC2_AXI_1F (CODA9_GDMA_BASE + 0x91c) +#define CODA9_GDI_TILEDBUF_BASE (CODA9_GDMA_BASE + 0x920) #endif diff --git a/drivers/media/platform/coda/trace.h b/drivers/media/platform/coda/trace.h index 781bf7286..d9099a0f7 100644 --- a/drivers/media/platform/coda/trace.h +++ b/drivers/media/platform/coda/trace.h @@ -48,7 +48,7 @@ TRACE_EVENT(coda_bit_done, TP_printk("minor = %d, ctx = %d", __entry->minor, __entry->ctx) ); -TRACE_EVENT(coda_enc_pic_run, +DECLARE_EVENT_CLASS(coda_buf_class, TP_PROTO(struct coda_ctx *ctx, struct vb2_buffer *buf), TP_ARGS(ctx, buf), @@ -69,28 +69,17 @@ TRACE_EVENT(coda_enc_pic_run, __entry->minor, __entry->index, __entry->ctx) ); -TRACE_EVENT(coda_enc_pic_done, +DEFINE_EVENT(coda_buf_class, coda_enc_pic_run, TP_PROTO(struct coda_ctx *ctx, struct vb2_buffer *buf), + TP_ARGS(ctx, buf) +); - TP_ARGS(ctx, buf), - - TP_STRUCT__entry( - __field(int, minor) - __field(int, index) - __field(int, ctx) - ), - - TP_fast_assign( - __entry->minor = ctx->fh.vdev->minor; - __entry->index = buf->v4l2_buf.index; - __entry->ctx = ctx->idx; - ), - - TP_printk("minor = %d, index = %d, ctx = %d", - __entry->minor, __entry->index, __entry->ctx) +DEFINE_EVENT(coda_buf_class, coda_enc_pic_done, + TP_PROTO(struct coda_ctx *ctx, struct vb2_buffer *buf), + TP_ARGS(ctx, buf) ); -TRACE_EVENT(coda_bit_queue, +DECLARE_EVENT_CLASS(coda_buf_meta_class, TP_PROTO(struct coda_ctx *ctx, struct vb2_buffer *buf, struct coda_buffer_meta *meta), @@ -117,7 +106,13 @@ TRACE_EVENT(coda_bit_queue, __entry->ctx) ); -TRACE_EVENT(coda_dec_pic_run, +DEFINE_EVENT(coda_buf_meta_class, coda_bit_queue, + TP_PROTO(struct coda_ctx *ctx, struct vb2_buffer *buf, + struct coda_buffer_meta *meta), + TP_ARGS(ctx, buf, meta) +); + +DECLARE_EVENT_CLASS(coda_meta_class, TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta), TP_ARGS(ctx, meta), @@ -140,54 +135,20 @@ TRACE_EVENT(coda_dec_pic_run, __entry->minor, __entry->start, __entry->end, __entry->ctx) ); -TRACE_EVENT(coda_dec_pic_done, +DEFINE_EVENT(coda_meta_class, coda_dec_pic_run, TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta), - - TP_ARGS(ctx, meta), - - TP_STRUCT__entry( - __field(int, minor) - __field(int, start) - __field(int, end) - __field(int, ctx) - ), - - TP_fast_assign( - __entry->minor = ctx->fh.vdev->minor; - __entry->start = meta->start; - __entry->end = meta->end; - __entry->ctx = ctx->idx; - ), - - TP_printk("minor = %d, start = 0x%x, end = 0x%x, ctx = %d", - __entry->minor, __entry->start, __entry->end, __entry->ctx) + TP_ARGS(ctx, meta) ); -TRACE_EVENT(coda_dec_rot_done, - TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta, - struct vb2_buffer *buf), - - TP_ARGS(ctx, meta, buf), - - TP_STRUCT__entry( - __field(int, minor) - __field(int, start) - __field(int, end) - __field(int, index) - __field(int, ctx) - ), - - TP_fast_assign( - __entry->minor = ctx->fh.vdev->minor; - __entry->start = meta->start; - __entry->end = meta->end; - __entry->index = buf->v4l2_buf.index; - __entry->ctx = ctx->idx; - ), +DEFINE_EVENT(coda_meta_class, coda_dec_pic_done, + TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta), + TP_ARGS(ctx, meta) +); - TP_printk("minor = %d, start = 0x%x, end = 0x%x, index = %d, ctx = %d", - __entry->minor, __entry->start, __entry->end, __entry->index, - __entry->ctx) +DEFINE_EVENT(coda_buf_meta_class, coda_dec_rot_done, + TP_PROTO(struct coda_ctx *ctx, struct vb2_buffer *buf, + struct coda_buffer_meta *meta), + TP_ARGS(ctx, buf, meta) ); #endif /* __CODA_TRACE_H__ */ diff --git a/drivers/media/platform/exynos4-is/fimc-m2m.c b/drivers/media/platform/exynos4-is/fimc-m2m.c index 0ad1b6f84..d2bfe7c2a 100644 --- a/drivers/media/platform/exynos4-is/fimc-m2m.c +++ b/drivers/media/platform/exynos4-is/fimc-m2m.c @@ -188,7 +188,7 @@ static int fimc_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, if (IS_ERR(f)) return PTR_ERR(f); /* - * Return number of non-contigous planes (plane buffers) + * Return number of non-contiguous planes (plane buffers) * depending on the configured color format. */ if (!f->fmt) diff --git a/drivers/media/platform/fsl-viu.c b/drivers/media/platform/fsl-viu.c index 5b76e3db6..ae8c6b35a 100644 --- a/drivers/media/platform/fsl-viu.c +++ b/drivers/media/platform/fsl-viu.c @@ -28,6 +28,9 @@ #include <media/v4l2-common.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> #include <media/videobuf-dma-contig.h> #define DRV_NAME "fsl_viu" @@ -40,49 +43,6 @@ /* I2C address of video decoder chip is 0x4A */ #define VIU_VIDEO_DECODER_ADDR 0x25 -/* supported controls */ -static struct v4l2_queryctrl viu_qctrl[] = { - { - .id = V4L2_CID_BRIGHTNESS, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Brightness", - .minimum = 0, - .maximum = 255, - .step = 1, - .default_value = 127, - .flags = 0, - }, { - .id = V4L2_CID_CONTRAST, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Contrast", - .minimum = 0, - .maximum = 255, - .step = 0x1, - .default_value = 0x10, - .flags = 0, - }, { - .id = V4L2_CID_SATURATION, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Saturation", - .minimum = 0, - .maximum = 255, - .step = 0x1, - .default_value = 127, - .flags = 0, - }, { - .id = V4L2_CID_HUE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Hue", - .minimum = -128, - .maximum = 127, - .step = 0x1, - .default_value = 0, - .flags = 0, - } -}; - -static int qctl_regs[ARRAY_SIZE(viu_qctrl)]; - static int info_level; #define dprintk(level, fmt, arg...) \ @@ -95,7 +55,6 @@ static int info_level; * Basic structures */ struct viu_fmt { - char name[32]; u32 fourcc; /* v4l2 format id */ u32 pixelformat; int depth; @@ -103,12 +62,10 @@ struct viu_fmt { static struct viu_fmt formats[] = { { - .name = "RGB-16 (5/B-6/G-5/R)", .fourcc = V4L2_PIX_FMT_RGB565, .pixelformat = V4L2_PIX_FMT_RGB565, .depth = 16, }, { - .name = "RGB-32 (A-R-G-B)", .fourcc = V4L2_PIX_FMT_RGB32, .pixelformat = V4L2_PIX_FMT_RGB32, .depth = 32, @@ -156,6 +113,7 @@ struct viu_reg { struct viu_dev { struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; struct mutex lock; spinlock_t slock; int users; @@ -195,6 +153,8 @@ struct viu_dev { }; struct viu_fh { + /* must remain the first field of this struct */ + struct v4l2_fh fh; struct viu_dev *dev; /* video capture */ @@ -604,6 +564,7 @@ static int vidioc_querycap(struct file *file, void *priv, { strcpy(cap->driver, "viu"); strcpy(cap->card, "viu"); + strcpy(cap->bus_info, "platform:viu"); cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OVERLAY | @@ -617,10 +578,9 @@ static int vidioc_enum_fmt(struct file *file, void *priv, { int index = f->index; - if (f->index > NUM_FORMATS) + if (f->index >= NUM_FORMATS) return -EINVAL; - strlcpy(f->description, formats[index].name, sizeof(f->description)); f->pixelformat = formats[index].fourcc; return 0; } @@ -637,6 +597,7 @@ static int vidioc_g_fmt_cap(struct file *file, void *priv, f->fmt.pix.bytesperline = (f->fmt.pix.width * fh->fmt->depth) >> 3; f->fmt.pix.sizeimage = fh->sizeimage; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; return 0; } @@ -644,7 +605,6 @@ static int vidioc_try_fmt_cap(struct file *file, void *priv, struct v4l2_format *f) { struct viu_fmt *fmt; - enum v4l2_field field; unsigned int maxw, maxh; fmt = format_by_fourcc(f->fmt.pix.pixelformat); @@ -654,19 +614,10 @@ static int vidioc_try_fmt_cap(struct file *file, void *priv, return -EINVAL; } - field = f->fmt.pix.field; - - if (field == V4L2_FIELD_ANY) { - field = V4L2_FIELD_INTERLACED; - } else if (field != V4L2_FIELD_INTERLACED) { - dprintk(1, "Field type invalid.\n"); - return -EINVAL; - } - maxw = norm_maxw(); maxh = norm_maxh(); - f->fmt.pix.field = field; + f->fmt.pix.field = V4L2_FIELD_INTERLACED; if (f->fmt.pix.height < 32) f->fmt.pix.height = 32; if (f->fmt.pix.height > maxh) @@ -678,6 +629,8 @@ static int vidioc_try_fmt_cap(struct file *file, void *priv, f->fmt.pix.width &= ~0x03; f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; return 0; } @@ -698,7 +651,6 @@ static int vidioc_s_fmt_cap(struct file *file, void *priv, fh->sizeimage = f->fmt.pix.sizeimage; fh->vb_vidq.field = f->fmt.pix.field; fh->type = f->type; - dprintk(1, "set to pixelformat '%4.6s'\n", (char *)&fh->fmt->name); return 0; } @@ -764,8 +716,8 @@ static int viu_setup_preview(struct viu_dev *dev, struct viu_fh *fh) { int bpp; - dprintk(1, "%s %dx%d %s\n", __func__, - fh->win.w.width, fh->win.w.height, dev->ovfmt->name); + dprintk(1, "%s %dx%d\n", __func__, + fh->win.w.width, fh->win.w.height); reg_val.status_cfg = 0; @@ -1002,58 +954,13 @@ static int vidioc_s_input(struct file *file, void *priv, unsigned int i) { struct viu_fh *fh = priv; - if (i > 1) + if (i) return -EINVAL; decoder_call(fh->dev, video, s_routing, i, 0, 0); return 0; } -/* Controls */ -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) { - if (qc->id && qc->id == viu_qctrl[i].id) { - memcpy(qc, &(viu_qctrl[i]), sizeof(*qc)); - return 0; - } - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) { - if (ctrl->id == viu_qctrl[i].id) { - ctrl->value = qctl_regs[i]; - return 0; - } - } - return -EINVAL; -} -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) { - if (ctrl->id == viu_qctrl[i].id) { - if (ctrl->value < viu_qctrl[i].minimum - || ctrl->value > viu_qctrl[i].maximum) - return -ERANGE; - qctl_regs[i] = ctrl->value; - return 0; - } - } - return -EINVAL; -} - inline void viu_activate_next_buf(struct viu_dev *dev, struct viu_dmaqueue *viuq) { @@ -1265,7 +1172,6 @@ static int viu_open(struct file *file) struct viu_reg *vr; int minor = vdev->minor; u32 status_cfg; - int i; dprintk(1, "viu: open (minor=%d)\n", minor); @@ -1293,6 +1199,7 @@ static int viu_open(struct file *file) return -ENOMEM; } + v4l2_fh_init(&fh->fh, vdev); file->private_data = fh; fh->dev = dev; @@ -1303,10 +1210,6 @@ static int viu_open(struct file *file) dev->crop_current.width = fh->width; dev->crop_current.height = fh->height; - /* Put all controls at a sane state */ - for (i = 0; i < ARRAY_SIZE(viu_qctrl); i++) - qctl_regs[i] = viu_qctrl[i].default_value; - dprintk(1, "Open: fh=0x%08lx, dev=0x%08lx, dev->vidq=0x%08lx\n", (unsigned long)fh, (unsigned long)dev, (unsigned long)&dev->vidq); @@ -1332,6 +1235,7 @@ static int viu_open(struct file *file) fh->type, V4L2_FIELD_INTERLACED, sizeof(struct viu_buf), fh, &fh->dev->lock); + v4l2_fh_add(&fh->fh); mutex_unlock(&dev->lock); return 0; } @@ -1364,13 +1268,17 @@ static unsigned int viu_poll(struct file *file, struct poll_table_struct *wait) struct viu_fh *fh = file->private_data; struct videobuf_queue *q = &fh->vb_vidq; struct viu_dev *dev = fh->dev; - unsigned int res; + unsigned long req_events = poll_requested_events(wait); + unsigned int res = v4l2_ctrl_poll(file, wait); if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) return POLLERR; + if (!(req_events & (POLLIN | POLLRDNORM))) + return res; + mutex_lock(&dev->lock); - res = videobuf_poll_stream(file, q, wait); + res |= videobuf_poll_stream(file, q, wait); mutex_unlock(&dev->lock); return res; } @@ -1385,6 +1293,8 @@ static int viu_release(struct file *file) viu_stop_dma(dev); videobuf_stop(&fh->vb_vidq); videobuf_mmap_free(&fh->vb_vidq); + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); mutex_unlock(&dev->lock); kfree(fh); @@ -1463,11 +1373,11 @@ static const struct v4l2_ioctl_ops viu_ioctl_ops = { .vidioc_enum_input = vidioc_enum_input, .vidioc_g_input = vidioc_g_input, .vidioc_s_input = vidioc_s_input, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, .vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static struct video_device viu_template = { @@ -1543,6 +1453,16 @@ static int viu_of_probe(struct platform_device *op) } ad = i2c_get_adapter(0); + + v4l2_ctrl_handler_init(&viu_dev->hdl, 5); + if (viu_dev->hdl.error) { + ret = viu_dev->hdl.error; + dev_err(&op->dev, "couldn't register control\n"); + goto err_vdev; + } + /* This control handler will inherit the control(s) from the + sub-device(s). */ + viu_dev->v4l2_dev.ctrl_handler = &viu_dev->hdl; viu_dev->decoder = v4l2_i2c_new_subdev(&viu_dev->v4l2_dev, ad, "saa7113", VIU_VIDEO_DECODER_ADDR, NULL); @@ -1559,7 +1479,7 @@ static int viu_of_probe(struct platform_device *op) goto err_vdev; } - memcpy(vdev, &viu_template, sizeof(viu_template)); + *vdev = viu_template; vdev->v4l2_dev = &viu_dev->v4l2_dev; @@ -1614,6 +1534,7 @@ err_irq: err_clk: video_unregister_device(viu_dev->vdev); err_vdev: + v4l2_ctrl_handler_free(&viu_dev->hdl); mutex_unlock(&viu_dev->lock); i2c_put_adapter(ad); v4l2_device_unregister(&viu_dev->v4l2_dev); @@ -1635,6 +1556,7 @@ static int viu_of_remove(struct platform_device *op) clk_disable_unprepare(dev->clk); + v4l2_ctrl_handler_free(&dev->hdl); video_unregister_device(dev->vdev); i2c_put_adapter(client->adapter); v4l2_device_unregister(&dev->v4l2_dev); diff --git a/drivers/media/platform/omap/Kconfig b/drivers/media/platform/omap/Kconfig index dc2aaab54..217d613b0 100644 --- a/drivers/media/platform/omap/Kconfig +++ b/drivers/media/platform/omap/Kconfig @@ -10,6 +10,7 @@ config VIDEO_OMAP2_VOUT select OMAP2_DSS if HAS_IOMEM && ARCH_OMAP2PLUS select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 select VIDEO_OMAP2_VOUT_VRFB if VIDEO_OMAP2_VOUT && OMAP2_VRFB + select FRAME_VECTOR default n ---help--- V4L2 Display driver support for OMAP2/3 based boards. diff --git a/drivers/media/platform/omap/omap_vout.c b/drivers/media/platform/omap/omap_vout.c index f09c5f17a..70c28d19e 100644 --- a/drivers/media/platform/omap/omap_vout.c +++ b/drivers/media/platform/omap/omap_vout.c @@ -195,46 +195,34 @@ static int omap_vout_try_format(struct v4l2_pix_format *pix) } /* - * omap_vout_uservirt_to_phys: This inline function is used to convert user - * space virtual address to physical address. + * omap_vout_get_userptr: Convert user space virtual address to physical + * address. */ -static unsigned long omap_vout_uservirt_to_phys(unsigned long virtp) +static int omap_vout_get_userptr(struct videobuf_buffer *vb, u32 virtp, + u32 *physp) { - unsigned long physp = 0; - struct vm_area_struct *vma; - struct mm_struct *mm = current->mm; + struct frame_vector *vec; + int ret; /* For kernel direct-mapped memory, take the easy way */ - if (virtp >= PAGE_OFFSET) - return virt_to_phys((void *) virtp); - - down_read(¤t->mm->mmap_sem); - vma = find_vma(mm, virtp); - if (vma && (vma->vm_flags & VM_IO) && vma->vm_pgoff) { - /* this will catch, kernel-allocated, mmaped-to-usermode - addresses */ - physp = (vma->vm_pgoff << PAGE_SHIFT) + (virtp - vma->vm_start); - up_read(¤t->mm->mmap_sem); - } else { - /* otherwise, use get_user_pages() for general userland pages */ - int res, nr_pages = 1; - struct page *pages; + if (virtp >= PAGE_OFFSET) { + *physp = virt_to_phys((void *)virtp); + return 0; + } - res = get_user_pages(current, current->mm, virtp, nr_pages, 1, - 0, &pages, NULL); - up_read(¤t->mm->mmap_sem); + vec = frame_vector_create(1); + if (!vec) + return -ENOMEM; - if (res == nr_pages) { - physp = __pa(page_address(&pages[0]) + - (virtp & ~PAGE_MASK)); - } else { - printk(KERN_WARNING VOUT_NAME - "get_user_pages failed\n"); - return 0; - } + ret = get_vaddr_frames(virtp, 1, true, false, vec); + if (ret != 1) { + frame_vector_destroy(vec); + return -EINVAL; } + *physp = __pfn_to_phys(frame_vector_pfns(vec)[0]); + vb->priv = vec; - return physp; + return 0; } /* @@ -784,11 +772,15 @@ static int omap_vout_buffer_prepare(struct videobuf_queue *q, * address of the buffer */ if (V4L2_MEMORY_USERPTR == vb->memory) { + int ret; + if (0 == vb->baddr) return -EINVAL; /* Physical address */ - vout->queued_buf_addr[vb->i] = (u8 *) - omap_vout_uservirt_to_phys(vb->baddr); + ret = omap_vout_get_userptr(vb, vb->baddr, + (u32 *)&vout->queued_buf_addr[vb->i]); + if (ret < 0) + return ret; } else { unsigned long addr, dma_addr; unsigned long size; @@ -834,12 +826,13 @@ static void omap_vout_buffer_queue(struct videobuf_queue *q, static void omap_vout_buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) { - struct omap_vout_device *vout = q->priv_data; - vb->state = VIDEOBUF_NEEDS_INIT; + if (vb->memory == V4L2_MEMORY_USERPTR && vb->priv) { + struct frame_vector *vec = vb->priv; - if (V4L2_MEMORY_MMAP != vout->memory) - return; + put_vaddr_frames(vec); + frame_vector_destroy(vec); + } } /* @@ -872,7 +865,7 @@ static void omap_vout_vm_close(struct vm_area_struct *vma) vout->mmap_count--; } -static struct vm_operations_struct omap_vout_vm_ops = { +static const struct vm_operations_struct omap_vout_vm_ops = { .open = omap_vout_vm_open, .close = omap_vout_vm_close, }; diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c index 12be830d7..56e683b19 100644 --- a/drivers/media/platform/omap3isp/isp.c +++ b/drivers/media/platform/omap3isp/isp.c @@ -101,7 +101,6 @@ static const struct isp_res_mapping isp_res_maps[] = { 0x0000, /* csi2a, len 0x0170 */ 0x0170, /* csiphy2, len 0x000c */ }, - .syscon_offset = 0xdc, .phy_type = ISP_PHY_TYPE_3430, }, { @@ -124,7 +123,6 @@ static const struct isp_res_mapping isp_res_maps[] = { 0x0570, /* csiphy1, len 0x000c */ 0x05c0, /* csi2c, len 0x0040 (2nd area) */ }, - .syscon_offset = 0x2f0, .phy_type = ISP_PHY_TYPE_3630, }, }; @@ -1796,47 +1794,6 @@ static void isp_unregister_entities(struct isp_device *isp) media_device_unregister(&isp->media_dev); } -/* - * isp_register_subdev - Register a sub-device - * @isp: OMAP3 ISP device - * @isp_subdev: platform data related to a sub-device - * - * Register an I2C sub-device which has not been registered by other - * means (such as the Device Tree). - * - * Return a pointer to the sub-device if it has been successfully - * registered, or NULL otherwise. - */ -static struct v4l2_subdev * -isp_register_subdev(struct isp_device *isp, - struct isp_platform_subdev *isp_subdev) -{ - struct i2c_adapter *adapter; - struct v4l2_subdev *sd; - - if (isp_subdev->board_info == NULL) - return NULL; - - adapter = i2c_get_adapter(isp_subdev->i2c_adapter_id); - if (adapter == NULL) { - dev_err(isp->dev, - "%s: Unable to get I2C adapter %d for device %s\n", - __func__, isp_subdev->i2c_adapter_id, - isp_subdev->board_info->type); - return NULL; - } - - sd = v4l2_i2c_new_subdev_board(&isp->v4l2_dev, adapter, - isp_subdev->board_info, NULL); - if (sd == NULL) { - dev_err(isp->dev, "%s: Unable to register subdev %s\n", - __func__, isp_subdev->board_info->type); - return NULL; - } - - return sd; -} - static int isp_link_entity( struct isp_device *isp, struct media_entity *entity, enum isp_interface_type interface) @@ -1910,8 +1867,6 @@ static int isp_link_entity( static int isp_register_entities(struct isp_device *isp) { - struct isp_platform_data *pdata = isp->pdata; - struct isp_platform_subdev *isp_subdev; int ret; isp->media_dev.dev = isp->dev; @@ -1968,37 +1923,6 @@ static int isp_register_entities(struct isp_device *isp) if (ret < 0) goto done; - /* - * Device Tree --- the external sub-devices will be registered - * later. The same goes for the sub-device node registration. - */ - if (isp->dev->of_node) - return 0; - - /* Register external entities */ - for (isp_subdev = pdata ? pdata->subdevs : NULL; - isp_subdev && isp_subdev->board_info; isp_subdev++) { - struct v4l2_subdev *sd; - - sd = isp_register_subdev(isp, isp_subdev); - - /* - * No bus information --- this is either a flash or a - * lens subdev. - */ - if (!sd || !isp_subdev->bus) - continue; - - sd->host_priv = isp_subdev->bus; - - ret = isp_link_entity(isp, &sd->entity, - isp_subdev->bus->interface); - if (ret < 0) - goto done; - } - - ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev); - done: if (ret < 0) isp_unregister_entities(isp); @@ -2402,33 +2326,24 @@ static int isp_probe(struct platform_device *pdev) return -ENOMEM; } - if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { - ret = of_property_read_u32(pdev->dev.of_node, "ti,phy-type", - &isp->phy_type); - if (ret) - return ret; + ret = of_property_read_u32(pdev->dev.of_node, "ti,phy-type", + &isp->phy_type); + if (ret) + return ret; - isp->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, - "syscon"); - if (IS_ERR(isp->syscon)) - return PTR_ERR(isp->syscon); + isp->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "syscon"); + if (IS_ERR(isp->syscon)) + return PTR_ERR(isp->syscon); - ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, - &isp->syscon_offset); - if (ret) - return ret; + ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, + &isp->syscon_offset); + if (ret) + return ret; - ret = isp_of_parse_nodes(&pdev->dev, &isp->notifier); - if (ret < 0) - return ret; - } else { - isp->pdata = pdev->dev.platform_data; - isp->syscon = syscon_regmap_lookup_by_pdevname("syscon.0"); - if (IS_ERR(isp->syscon)) - return PTR_ERR(isp->syscon); - dev_warn(&pdev->dev, - "Platform data support is deprecated! Please move to DT now!\n"); - } + ret = isp_of_parse_nodes(&pdev->dev, &isp->notifier); + if (ret < 0) + return ret; isp->autoidle = autoidle; @@ -2507,11 +2422,6 @@ static int isp_probe(struct platform_device *pdev) goto error_isp; } - if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) { - isp->syscon_offset = isp_res_maps[m].syscon_offset; - isp->phy_type = isp_res_maps[m].phy_type; - } - for (i = 1; i < OMAP3_ISP_IOMEM_CSI2A_REGS1; i++) isp->mmio_base[i] = isp->mmio_base[0] + isp_res_maps[m].offset[i]; @@ -2555,15 +2465,12 @@ static int isp_probe(struct platform_device *pdev) if (ret < 0) goto error_modules; - if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { - isp->notifier.bound = isp_subdev_notifier_bound; - isp->notifier.complete = isp_subdev_notifier_complete; + isp->notifier.bound = isp_subdev_notifier_bound; + isp->notifier.complete = isp_subdev_notifier_complete; - ret = v4l2_async_notifier_register(&isp->v4l2_dev, - &isp->notifier); - if (ret) - goto error_register_entities; - } + ret = v4l2_async_notifier_register(&isp->v4l2_dev, &isp->notifier); + if (ret) + goto error_register_entities; isp_core_init(isp, 1); omap3isp_put(isp); diff --git a/drivers/media/platform/omap3isp/isp.h b/drivers/media/platform/omap3isp/isp.h index e57994317..5acc2e651 100644 --- a/drivers/media/platform/omap3isp/isp.h +++ b/drivers/media/platform/omap3isp/isp.h @@ -17,7 +17,6 @@ #ifndef OMAP3_ISP_CORE_H #define OMAP3_ISP_CORE_H -#include <media/omap3isp.h> #include <media/v4l2-async.h> #include <media/v4l2-device.h> #include <linux/clk-provider.h> @@ -27,6 +26,7 @@ #include <linux/platform_device.h> #include <linux/wait.h> +#include "omap3isp.h" #include "ispstat.h" #include "ispccdc.h" #include "ispreg.h" @@ -101,15 +101,11 @@ struct regmap; * struct isp_res_mapping - Map ISP io resources to ISP revision. * @isp_rev: ISP_REVISION_x_x * @offset: register offsets of various ISP sub-blocks - * @syscon_offset: offset of the syscon register for 343x / 3630 - * (CONTROL_CSIRXFE / CONTROL_CAMERA_PHY_CTRL, respectively) - * from the syscon base address * @phy_type: ISP_PHY_TYPE_{3430,3630} */ struct isp_res_mapping { u32 isp_rev; u32 offset[OMAP3_ISP_IOMEM_LAST]; - u32 syscon_offset; u32 phy_type; }; @@ -184,7 +180,6 @@ struct isp_device { u32 revision; /* platform HW resources */ - struct isp_platform_data *pdata; unsigned int irq_num; void __iomem *mmio_base[OMAP3_ISP_IOMEM_LAST]; diff --git a/drivers/media/platform/omap3isp/ispcsiphy.h b/drivers/media/platform/omap3isp/ispcsiphy.h index e17c88bea..28b63b28f 100644 --- a/drivers/media/platform/omap3isp/ispcsiphy.h +++ b/drivers/media/platform/omap3isp/ispcsiphy.h @@ -17,7 +17,7 @@ #ifndef OMAP3_ISP_CSI_PHY_H #define OMAP3_ISP_CSI_PHY_H -#include <media/omap3isp.h> +#include "omap3isp.h" struct isp_csi2_device; struct regulator; diff --git a/drivers/media/platform/omap3isp/ispvideo.c b/drivers/media/platform/omap3isp/ispvideo.c index d285af18d..41bb8df91 100644 --- a/drivers/media/platform/omap3isp/ispvideo.c +++ b/drivers/media/platform/omap3isp/ispvideo.c @@ -1018,8 +1018,7 @@ isp_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) pipe->entities = 0; - if (video->isp->pdata && video->isp->pdata->set_constraints) - video->isp->pdata->set_constraints(video->isp, true); + /* TODO: Implement PM QoS */ pipe->l3_ick = clk_get_rate(video->isp->clock[ISP_CLK_L3_ICK]); pipe->max_rate = pipe->l3_ick; @@ -1100,8 +1099,7 @@ err_set_stream: err_check_format: media_entity_pipeline_stop(&video->video.entity); err_pipeline_start: - if (video->isp->pdata && video->isp->pdata->set_constraints) - video->isp->pdata->set_constraints(video->isp, false); + /* TODO: Implement PM QoS */ /* The DMA queue must be emptied here, otherwise CCDC interrupts that * will get triggered the next time the CCDC is powered up will try to * access buffers that might have been freed but still present in the @@ -1161,8 +1159,7 @@ isp_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) video->queue = NULL; video->error = false; - if (video->isp->pdata && video->isp->pdata->set_constraints) - video->isp->pdata->set_constraints(video->isp, false); + /* TODO: Implement PM QoS */ media_entity_pipeline_stop(&video->video.entity); done: diff --git a/drivers/media/platform/omap3isp/omap3isp.h b/drivers/media/platform/omap3isp/omap3isp.h new file mode 100644 index 000000000..190e259a6 --- /dev/null +++ b/drivers/media/platform/omap3isp/omap3isp.h @@ -0,0 +1,132 @@ +/* + * omap3isp.h + * + * TI OMAP3 ISP - Bus Configuration + * + * Copyright (C) 2011 Nokia Corporation + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Sakari Ailus <sakari.ailus@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __OMAP3ISP_H__ +#define __OMAP3ISP_H__ + +enum isp_interface_type { + ISP_INTERFACE_PARALLEL, + ISP_INTERFACE_CSI2A_PHY2, + ISP_INTERFACE_CCP2B_PHY1, + ISP_INTERFACE_CCP2B_PHY2, + ISP_INTERFACE_CSI2C_PHY1, +}; + +/** + * struct isp_parallel_cfg - Parallel interface configuration + * @data_lane_shift: Data lane shifter + * 0 - CAMEXT[13:0] -> CAM[13:0] + * 1 - CAMEXT[13:2] -> CAM[11:0] + * 2 - CAMEXT[13:4] -> CAM[9:0] + * 3 - CAMEXT[13:6] -> CAM[7:0] + * @clk_pol: Pixel clock polarity + * 0 - Sample on rising edge, 1 - Sample on falling edge + * @hs_pol: Horizontal synchronization polarity + * 0 - Active high, 1 - Active low + * @vs_pol: Vertical synchronization polarity + * 0 - Active high, 1 - Active low + * @fld_pol: Field signal polarity + * 0 - Positive, 1 - Negative + * @data_pol: Data polarity + * 0 - Normal, 1 - One's complement + */ +struct isp_parallel_cfg { + unsigned int data_lane_shift:2; + unsigned int clk_pol:1; + unsigned int hs_pol:1; + unsigned int vs_pol:1; + unsigned int fld_pol:1; + unsigned int data_pol:1; +}; + +enum { + ISP_CCP2_PHY_DATA_CLOCK = 0, + ISP_CCP2_PHY_DATA_STROBE = 1, +}; + +enum { + ISP_CCP2_MODE_MIPI = 0, + ISP_CCP2_MODE_CCP2 = 1, +}; + +/** + * struct isp_csiphy_lane: CCP2/CSI2 lane position and polarity + * @pos: position of the lane + * @pol: polarity of the lane + */ +struct isp_csiphy_lane { + u8 pos; + u8 pol; +}; + +#define ISP_CSIPHY1_NUM_DATA_LANES 1 +#define ISP_CSIPHY2_NUM_DATA_LANES 2 + +/** + * struct isp_csiphy_lanes_cfg - CCP2/CSI2 lane configuration + * @data: Configuration of one or two data lanes + * @clk: Clock lane configuration + */ +struct isp_csiphy_lanes_cfg { + struct isp_csiphy_lane data[ISP_CSIPHY2_NUM_DATA_LANES]; + struct isp_csiphy_lane clk; +}; + +/** + * struct isp_ccp2_cfg - CCP2 interface configuration + * @strobe_clk_pol: Strobe/clock polarity + * 0 - Non Inverted, 1 - Inverted + * @crc: Enable the cyclic redundancy check + * @ccp2_mode: Enable CCP2 compatibility mode + * ISP_CCP2_MODE_MIPI - MIPI-CSI1 mode + * ISP_CCP2_MODE_CCP2 - CCP2 mode + * @phy_layer: Physical layer selection + * ISP_CCP2_PHY_DATA_CLOCK - Data/clock physical layer + * ISP_CCP2_PHY_DATA_STROBE - Data/strobe physical layer + * @vpclk_div: Video port output clock control + */ +struct isp_ccp2_cfg { + unsigned int strobe_clk_pol:1; + unsigned int crc:1; + unsigned int ccp2_mode:1; + unsigned int phy_layer:1; + unsigned int vpclk_div:2; + struct isp_csiphy_lanes_cfg lanecfg; +}; + +/** + * struct isp_csi2_cfg - CSI2 interface configuration + * @crc: Enable the cyclic redundancy check + */ +struct isp_csi2_cfg { + unsigned crc:1; + struct isp_csiphy_lanes_cfg lanecfg; +}; + +struct isp_bus_cfg { + enum isp_interface_type interface; + union { + struct isp_parallel_cfg parallel; + struct isp_ccp2_cfg ccp2; + struct isp_csi2_cfg csi2; + } bus; /* gcc < 4.6.0 chokes on anonymous union initializers */ +}; + +#endif /* __OMAP3ISP_H__ */ diff --git a/drivers/media/platform/rcar_jpu.c b/drivers/media/platform/rcar_jpu.c new file mode 100644 index 000000000..2973f070d --- /dev/null +++ b/drivers/media/platform/rcar_jpu.c @@ -0,0 +1,1794 @@ +/* + * Author: Mikhail Ulyanov + * Copyright (C) 2014-2015 Cogent Embedded, Inc. <source@cogentembedded.com> + * Copyright (C) 2014-2015 Renesas Electronics Corporation + * + * This is based on the drivers/media/platform/s5p-jpeg driver by + * Andrzej Pietrasiewicz and Jacek Anaszewski. + * Some portions of code inspired by VSP1 driver by Laurent Pinchart. + * + * TODO in order of priority: + * 1) Rotation + * 2) Cropping + * 3) V4L2_CID_JPEG_ACTIVE_MARKER + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <asm/unaligned.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + + +#define DRV_NAME "rcar_jpu" + +/* + * Align JPEG header end to cache line to make sure we will not have any issues + * with cache; additionally to requerment (33.3.27 R01UH0501EJ0100 Rev.1.00) + */ +#define JPU_JPEG_HDR_SIZE (ALIGN(0x258, L1_CACHE_BYTES)) +#define JPU_JPEG_MAX_BYTES_PER_PIXEL 2 /* 16 bit precision format */ +#define JPU_JPEG_MIN_SIZE 25 /* SOI + SOF + EOI */ +#define JPU_JPEG_QTBL_SIZE 0x40 +#define JPU_JPEG_HDCTBL_SIZE 0x1c +#define JPU_JPEG_HACTBL_SIZE 0xb2 +#define JPU_JPEG_HEIGHT_OFFSET 0x91 +#define JPU_JPEG_WIDTH_OFFSET 0x93 +#define JPU_JPEG_SUBS_OFFSET 0x97 +#define JPU_JPEG_QTBL_LUM_OFFSET 0x07 +#define JPU_JPEG_QTBL_CHR_OFFSET 0x4c +#define JPU_JPEG_HDCTBL_LUM_OFFSET 0xa4 +#define JPU_JPEG_HACTBL_LUM_OFFSET 0xc5 +#define JPU_JPEG_HDCTBL_CHR_OFFSET 0x17c +#define JPU_JPEG_HACTBL_CHR_OFFSET 0x19d +#define JPU_JPEG_PADDING_OFFSET 0x24f +#define JPU_JPEG_LUM 0x00 +#define JPU_JPEG_CHR 0x01 +#define JPU_JPEG_DC 0x00 +#define JPU_JPEG_AC 0x10 + +#define JPU_JPEG_422 0x21 +#define JPU_JPEG_420 0x22 + +#define JPU_JPEG_DEFAULT_422_PIX_FMT V4L2_PIX_FMT_NV16M +#define JPU_JPEG_DEFAULT_420_PIX_FMT V4L2_PIX_FMT_NV12M + +/* JPEG markers */ +#define TEM 0x01 +#define SOF0 0xc0 +#define RST 0xd0 +#define SOI 0xd8 +#define EOI 0xd9 +#define DHP 0xde +#define DHT 0xc4 +#define COM 0xfe +#define DQT 0xdb +#define DRI 0xdd +#define APP0 0xe0 + +#define JPU_RESET_TIMEOUT 100 /* ms */ +#define JPU_JOB_TIMEOUT 300 /* ms */ +#define JPU_MAX_QUALITY 4 +#define JPU_WIDTH_MIN 16 +#define JPU_HEIGHT_MIN 16 +#define JPU_WIDTH_MAX 4096 +#define JPU_HEIGHT_MAX 4096 +#define JPU_MEMALIGN 8 + +/* Flags that indicate a format can be used for capture/output */ +#define JPU_FMT_TYPE_OUTPUT 0 +#define JPU_FMT_TYPE_CAPTURE 1 +#define JPU_ENC_CAPTURE (1 << 0) +#define JPU_ENC_OUTPUT (1 << 1) +#define JPU_DEC_CAPTURE (1 << 2) +#define JPU_DEC_OUTPUT (1 << 3) + +/* + * JPEG registers and bits + */ + +/* JPEG code mode register */ +#define JCMOD 0x00 +#define JCMOD_PCTR (1 << 7) +#define JCMOD_MSKIP_ENABLE (1 << 5) +#define JCMOD_DSP_ENC (0 << 3) +#define JCMOD_DSP_DEC (1 << 3) +#define JCMOD_REDU (7 << 0) +#define JCMOD_REDU_422 (1 << 0) +#define JCMOD_REDU_420 (2 << 0) + +/* JPEG code command register */ +#define JCCMD 0x04 +#define JCCMD_SRST (1 << 12) +#define JCCMD_JEND (1 << 2) +#define JCCMD_JSRT (1 << 0) + +/* JPEG code quantanization table number register */ +#define JCQTN 0x0c +#define JCQTN_SHIFT(t) (((t) - 1) << 1) + +/* JPEG code Huffman table number register */ +#define JCHTN 0x10 +#define JCHTN_AC_SHIFT(t) (((t) << 1) - 1) +#define JCHTN_DC_SHIFT(t) (((t) - 1) << 1) + +#define JCVSZU 0x1c /* JPEG code vertical size upper register */ +#define JCVSZD 0x20 /* JPEG code vertical size lower register */ +#define JCHSZU 0x24 /* JPEG code horizontal size upper register */ +#define JCHSZD 0x28 /* JPEG code horizontal size lower register */ +#define JCSZ_MASK 0xff /* JPEG code h/v size register contains only 1 byte*/ + +#define JCDTCU 0x2c /* JPEG code data count upper register */ +#define JCDTCM 0x30 /* JPEG code data count middle register */ +#define JCDTCD 0x34 /* JPEG code data count lower register */ + +/* JPEG interrupt enable register */ +#define JINTE 0x38 +#define JINTE_ERR (7 << 5) /* INT5 + INT6 + INT7 */ +#define JINTE_TRANSF_COMPL (1 << 10) + +/* JPEG interrupt status register */ +#define JINTS 0x3c +#define JINTS_MASK 0x7c68 +#define JINTS_ERR (1 << 5) +#define JINTS_PROCESS_COMPL (1 << 6) +#define JINTS_TRANSF_COMPL (1 << 10) + +#define JCDERR 0x40 /* JPEG code decode error register */ +#define JCDERR_MASK 0xf /* JPEG code decode error register mask*/ + +/* JPEG interface encoding */ +#define JIFECNT 0x70 +#define JIFECNT_INFT_422 0 +#define JIFECNT_INFT_420 1 +#define JIFECNT_SWAP_WB (3 << 4) /* to JPU */ + +#define JIFESYA1 0x74 /* encode source Y address register 1 */ +#define JIFESCA1 0x78 /* encode source C address register 1 */ +#define JIFESYA2 0x7c /* encode source Y address register 2 */ +#define JIFESCA2 0x80 /* encode source C address register 2 */ +#define JIFESMW 0x84 /* encode source memory width register */ +#define JIFESVSZ 0x88 /* encode source vertical size register */ +#define JIFESHSZ 0x8c /* encode source horizontal size register */ +#define JIFEDA1 0x90 /* encode destination address register 1 */ +#define JIFEDA2 0x94 /* encode destination address register 2 */ + +/* JPEG decoding control register */ +#define JIFDCNT 0xa0 +#define JIFDCNT_SWAP_WB (3 << 1) /* from JPU */ + +#define JIFDSA1 0xa4 /* decode source address register 1 */ +#define JIFDDMW 0xb0 /* decode destination memory width register */ +#define JIFDDVSZ 0xb4 /* decode destination vert. size register */ +#define JIFDDHSZ 0xb8 /* decode destination horiz. size register */ +#define JIFDDYA1 0xbc /* decode destination Y address register 1 */ +#define JIFDDCA1 0xc0 /* decode destination C address register 1 */ + +#define JCQTBL(n) (0x10000 + (n) * 0x40) /* quantization tables regs */ +#define JCHTBD(n) (0x10100 + (n) * 0x100) /* Huffman table DC regs */ +#define JCHTBA(n) (0x10120 + (n) * 0x100) /* Huffman table AC regs */ + +/** + * struct jpu - JPEG IP abstraction + * @mutex: the mutex protecting this structure + * @lock: spinlock protecting the device contexts + * @v4l2_dev: v4l2 device for mem2mem mode + * @vfd_encoder: video device node for encoder mem2mem mode + * @vfd_decoder: video device node for decoder mem2mem mode + * @m2m_dev: v4l2 mem2mem device data + * @curr: pointer to current context + * @irq_queue: interrupt handler waitqueue + * @regs: JPEG IP registers mapping + * @irq: JPEG IP irq + * @clk: JPEG IP clock + * @dev: JPEG IP struct device + * @alloc_ctx: videobuf2 memory allocator's context + * @ref_count: reference counter + */ +struct jpu { + struct mutex mutex; + spinlock_t lock; + struct v4l2_device v4l2_dev; + struct video_device vfd_encoder; + struct video_device vfd_decoder; + struct v4l2_m2m_dev *m2m_dev; + struct jpu_ctx *curr; + wait_queue_head_t irq_queue; + + void __iomem *regs; + unsigned int irq; + struct clk *clk; + struct device *dev; + void *alloc_ctx; + int ref_count; +}; + +/** + * struct jpu_buffer - driver's specific video buffer + * @buf: m2m buffer + * @compr_quality: destination image quality in compression mode + * @subsampling: source image subsampling in decompression mode + */ +struct jpu_buffer { + struct v4l2_m2m_buffer buf; + unsigned short compr_quality; + unsigned char subsampling; +}; + +/** + * struct jpu_fmt - driver's internal format data + * @fourcc: the fourcc code, 0 if not applicable + * @colorspace: the colorspace specifier + * @bpp: number of bits per pixel per plane + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @subsampling: (horizontal:4 | vertical:4) subsampling factor + * @num_planes: number of planes + * @types: types of queue this format is applicable to + */ +struct jpu_fmt { + u32 fourcc; + u32 colorspace; + u8 bpp[2]; + u8 h_align; + u8 v_align; + u8 subsampling; + u8 num_planes; + u16 types; +}; + +/** + * jpu_q_data - parameters of one queue + * @fmtinfo: driver-specific format of this queue + * @format: multiplanar format of this queue + * @sequence: sequence number + */ +struct jpu_q_data { + struct jpu_fmt *fmtinfo; + struct v4l2_pix_format_mplane format; + unsigned int sequence; +}; + +/** + * jpu_ctx - the device context data + * @jpu: JPEG IP device for this context + * @encoder: compression (encode) operation or decompression (decode) + * @compr_quality: destination image quality in compression (encode) mode + * @out_q: source (output) queue information + * @cap_q: destination (capture) queue information + * @fh: file handler + * @ctrl_handler: controls handler + */ +struct jpu_ctx { + struct jpu *jpu; + bool encoder; + unsigned short compr_quality; + struct jpu_q_data out_q; + struct jpu_q_data cap_q; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; +}; + + /** + * jpeg_buffer - description of memory containing input JPEG data + * @end: end position in the buffer + * @curr: current position in the buffer + */ +struct jpeg_buffer { + void *end; + void *curr; +}; + +static struct jpu_fmt jpu_formats[] = { + { V4L2_PIX_FMT_JPEG, V4L2_COLORSPACE_JPEG, + {0, 0}, 0, 0, 0, 1, JPU_ENC_CAPTURE | JPU_DEC_OUTPUT }, + { V4L2_PIX_FMT_NV16M, V4L2_COLORSPACE_SRGB, + {8, 8}, 2, 2, JPU_JPEG_422, 2, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, + { V4L2_PIX_FMT_NV12M, V4L2_COLORSPACE_SRGB, + {8, 4}, 2, 2, JPU_JPEG_420, 2, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, + { V4L2_PIX_FMT_NV16, V4L2_COLORSPACE_SRGB, + {16, 0}, 2, 2, JPU_JPEG_422, 1, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, + { V4L2_PIX_FMT_NV12, V4L2_COLORSPACE_SRGB, + {12, 0}, 2, 2, JPU_JPEG_420, 1, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, +}; + +static const u8 zigzag[] = { + 0x03, 0x02, 0x0b, 0x13, 0x0a, 0x01, 0x00, 0x09, + 0x12, 0x1b, 0x23, 0x1a, 0x11, 0x08, 0x07, 0x06, + 0x0f, 0x10, 0x19, 0x22, 0x2b, 0x33, 0x2a, 0x21, + 0x18, 0x17, 0x0e, 0x05, 0x04, 0x0d, 0x16, 0x1f, + 0x20, 0x29, 0x32, 0x3b, 0x3a, 0x31, 0x28, 0x27, + 0x1e, 0x15, 0x0e, 0x14, 0x10, 0x26, 0x2f, 0x30, + 0x39, 0x38, 0x37, 0x2e, 0x25, 0x1c, 0x24, 0x2b, + 0x36, 0x3f, 0x3e, 0x35, 0x2c, 0x34, 0x3d, 0x3c +}; + +#define QTBL_SIZE (ALIGN(JPU_JPEG_QTBL_SIZE, \ + sizeof(unsigned int)) / sizeof(unsigned int)) +#define HDCTBL_SIZE (ALIGN(JPU_JPEG_HDCTBL_SIZE, \ + sizeof(unsigned int)) / sizeof(unsigned int)) +#define HACTBL_SIZE (ALIGN(JPU_JPEG_HACTBL_SIZE, \ + sizeof(unsigned int)) / sizeof(unsigned int)) +/* + * Start of image; Quantization tables + * SOF0 (17 bytes payload) is Baseline DCT - Sample precision, height, width, + * Number of image components, (Ci:8 - Hi:4 - Vi:4 - Tq:8) * 3 - Y,Cb,Cr; + * Huffman tables; Padding with 0xff (33.3.27 R01UH0501EJ0100 Rev.1.00) + */ +#define JPU_JPEG_HDR_BLOB { \ + 0xff, SOI, 0xff, DQT, 0x00, JPU_JPEG_QTBL_SIZE + 0x3, JPU_JPEG_LUM, \ + [JPU_JPEG_QTBL_LUM_OFFSET ... \ + JPU_JPEG_QTBL_LUM_OFFSET + JPU_JPEG_QTBL_SIZE - 1] = 0x00, \ + 0xff, DQT, 0x00, JPU_JPEG_QTBL_SIZE + 0x3, JPU_JPEG_CHR, \ + [JPU_JPEG_QTBL_CHR_OFFSET ... JPU_JPEG_QTBL_CHR_OFFSET + \ + JPU_JPEG_QTBL_SIZE - 1] = 0x00, 0xff, SOF0, 0x00, 0x11, 0x08, \ + [JPU_JPEG_HEIGHT_OFFSET ... JPU_JPEG_HEIGHT_OFFSET + 1] = 0x00, \ + [JPU_JPEG_WIDTH_OFFSET ... JPU_JPEG_WIDTH_OFFSET + 1] = 0x00, \ + 0x03, 0x01, [JPU_JPEG_SUBS_OFFSET] = 0x00, JPU_JPEG_LUM, \ + 0x02, 0x11, JPU_JPEG_CHR, 0x03, 0x11, JPU_JPEG_CHR, \ + 0xff, DHT, 0x00, JPU_JPEG_HDCTBL_SIZE + 0x3, JPU_JPEG_LUM|JPU_JPEG_DC, \ + [JPU_JPEG_HDCTBL_LUM_OFFSET ... \ + JPU_JPEG_HDCTBL_LUM_OFFSET + JPU_JPEG_HDCTBL_SIZE - 1] = 0x00, \ + 0xff, DHT, 0x00, JPU_JPEG_HACTBL_SIZE + 0x3, JPU_JPEG_LUM|JPU_JPEG_AC, \ + [JPU_JPEG_HACTBL_LUM_OFFSET ... \ + JPU_JPEG_HACTBL_LUM_OFFSET + JPU_JPEG_HACTBL_SIZE - 1] = 0x00, \ + 0xff, DHT, 0x00, JPU_JPEG_HDCTBL_SIZE + 0x3, JPU_JPEG_CHR|JPU_JPEG_DC, \ + [JPU_JPEG_HDCTBL_CHR_OFFSET ... \ + JPU_JPEG_HDCTBL_CHR_OFFSET + JPU_JPEG_HDCTBL_SIZE - 1] = 0x00, \ + 0xff, DHT, 0x00, JPU_JPEG_HACTBL_SIZE + 0x3, JPU_JPEG_CHR|JPU_JPEG_AC, \ + [JPU_JPEG_HACTBL_CHR_OFFSET ... \ + JPU_JPEG_HACTBL_CHR_OFFSET + JPU_JPEG_HACTBL_SIZE - 1] = 0x00, \ + [JPU_JPEG_PADDING_OFFSET ... JPU_JPEG_HDR_SIZE - 1] = 0xff \ +} + +static unsigned char jpeg_hdrs[JPU_MAX_QUALITY][JPU_JPEG_HDR_SIZE] = { + [0 ... JPU_MAX_QUALITY - 1] = JPU_JPEG_HDR_BLOB +}; + +static const unsigned int qtbl_lum[JPU_MAX_QUALITY][QTBL_SIZE] = { + { + 0x14101927, 0x322e3e44, 0x10121726, 0x26354144, + 0x19171f26, 0x35414444, 0x27262635, 0x41444444, + 0x32263541, 0x44444444, 0x2e354144, 0x44444444, + 0x3e414444, 0x44444444, 0x44444444, 0x44444444 + }, + { + 0x100b0b10, 0x171b1f1e, 0x0b0c0c0f, 0x1417171e, + 0x0b0c0d10, 0x171a232f, 0x100f1017, 0x1a252f40, + 0x1714171a, 0x27334040, 0x1b171a25, 0x33404040, + 0x1f17232f, 0x40404040, 0x1e1e2f40, 0x40404040 + }, + { + 0x0c08080c, 0x11151817, 0x0809090b, 0x0f131217, + 0x08090a0c, 0x13141b24, 0x0c0b0c15, 0x141c2435, + 0x110f1314, 0x1e27333b, 0x1513141c, 0x27333b3b, + 0x18121b24, 0x333b3b3b, 0x17172435, 0x3b3b3b3b + }, + { + 0x08060608, 0x0c0e1011, 0x06060608, 0x0a0d0c0f, + 0x06060708, 0x0d0e1218, 0x0808080e, 0x0d131823, + 0x0c0a0d0d, 0x141a2227, 0x0e0d0e13, 0x1a222727, + 0x100c1318, 0x22272727, 0x110f1823, 0x27272727 + } +}; + +static const unsigned int qtbl_chr[JPU_MAX_QUALITY][QTBL_SIZE] = { + { + 0x15192026, 0x36444444, 0x191c1826, 0x36444444, + 0x2018202b, 0x42444444, 0x26262b35, 0x44444444, + 0x36424444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444 + }, + { + 0x110f1115, 0x141a2630, 0x0f131211, 0x141a232b, + 0x11121416, 0x1a1e2e35, 0x1511161c, 0x1e273540, + 0x14141a1e, 0x27304040, 0x1a1a1e27, 0x303f4040, + 0x26232e35, 0x40404040, 0x302b3540, 0x40404040 + }, + { + 0x0d0b0d10, 0x14141d25, 0x0b0e0e0e, 0x10141a20, + 0x0d0e0f11, 0x14172328, 0x100e1115, 0x171e2832, + 0x14101417, 0x1e25323b, 0x1414171e, 0x25303b3b, + 0x1d1a2328, 0x323b3b3b, 0x25202832, 0x3b3b3b3b + }, + { + 0x0908090b, 0x0e111318, 0x080a090b, 0x0e0d1116, + 0x09090d0e, 0x0d0f171a, 0x0b0b0e0e, 0x0f141a21, + 0x0e0e0d0f, 0x14182127, 0x110d0f14, 0x18202727, + 0x1311171a, 0x21272727, 0x18161a21, 0x27272727 + } +}; + +static const unsigned int hdctbl_lum[HDCTBL_SIZE] = { + 0x00010501, 0x01010101, 0x01000000, 0x00000000, + 0x00010203, 0x04050607, 0x08090a0b +}; + +static const unsigned int hdctbl_chr[HDCTBL_SIZE] = { + 0x00010501, 0x01010101, 0x01000000, 0x00000000, + 0x00010203, 0x04050607, 0x08090a0b +}; + +static const unsigned int hactbl_lum[HACTBL_SIZE] = { + 0x00020103, 0x03020403, 0x05050404, 0x0000017d, 0x01020300, 0x04110512, + 0x21314106, 0x13516107, 0x22711432, 0x8191a108, 0x2342b1c1, 0x1552d1f0, + 0x24336272, 0x82090a16, 0x1718191a, 0x25262728, 0x292a3435, 0x36373839, + 0x3a434445, 0x46474849, 0x4a535455, 0x56575859, 0x5a636465, 0x66676869, + 0x6a737475, 0x76777879, 0x7a838485, 0x86878889, 0x8a929394, 0x95969798, + 0x999aa2a3, 0xa4a5a6a7, 0xa8a9aab2, 0xb3b4b5b6, 0xb7b8b9ba, 0xc2c3c4c5, + 0xc6c7c8c9, 0xcad2d3d4, 0xd5d6d7d8, 0xd9dae1e2, 0xe3e4e5e6, 0xe7e8e9ea, + 0xf1f2f3f4, 0xf5f6f7f8, 0xf9fa0000 +}; + +static const unsigned int hactbl_chr[HACTBL_SIZE] = { + 0x00020103, 0x03020403, 0x05050404, 0x0000017d, 0x01020300, 0x04110512, + 0x21314106, 0x13516107, 0x22711432, 0x8191a108, 0x2342b1c1, 0x1552d1f0, + 0x24336272, 0x82090a16, 0x1718191a, 0x25262728, 0x292a3435, 0x36373839, + 0x3a434445, 0x46474849, 0x4a535455, 0x56575859, 0x5a636465, 0x66676869, + 0x6a737475, 0x76777879, 0x7a838485, 0x86878889, 0x8a929394, 0x95969798, + 0x999aa2a3, 0xa4a5a6a7, 0xa8a9aab2, 0xb3b4b5b6, 0xb7b8b9ba, 0xc2c3c4c5, + 0xc6c7c8c9, 0xcad2d3d4, 0xd5d6d7d8, 0xd9dae1e2, 0xe3e4e5e6, 0xe7e8e9ea, + 0xf1f2f3f4, 0xf5f6f7f8, 0xf9fa0000 +}; + +static const char *error_to_text[16] = { + "Normal", + "SOI not detected", + "SOF1 to SOFF detected", + "Subsampling not detected", + "SOF accuracy error", + "DQT accuracy error", + "Component error 1", + "Component error 2", + "SOF0, DQT, and DHT not detected when SOS detected", + "SOS not detected", + "EOI not detected", + "Restart interval data number error detected", + "Image size error", + "Last MCU data number error", + "Block data number error", + "Unknown" +}; + +static struct jpu_buffer *vb2_to_jpu_buffer(struct vb2_buffer *vb) +{ + struct v4l2_m2m_buffer *b = + container_of(vb, struct v4l2_m2m_buffer, vb); + + return container_of(b, struct jpu_buffer, buf); +} + +static u32 jpu_read(struct jpu *jpu, unsigned int reg) +{ + return ioread32(jpu->regs + reg); +} + +static void jpu_write(struct jpu *jpu, u32 val, unsigned int reg) +{ + iowrite32(val, jpu->regs + reg); +} + +static struct jpu_ctx *ctrl_to_ctx(struct v4l2_ctrl *c) +{ + return container_of(c->handler, struct jpu_ctx, ctrl_handler); +} + +static struct jpu_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct jpu_ctx, fh); +} + +static void jpu_set_tbl(struct jpu *jpu, u32 reg, const unsigned int *tbl, + unsigned int len) { + unsigned int i; + + for (i = 0; i < len; i++) + jpu_write(jpu, tbl[i], reg + (i << 2)); +} + +static void jpu_set_qtbl(struct jpu *jpu, unsigned short quality) +{ + jpu_set_tbl(jpu, JCQTBL(0), qtbl_lum[quality], QTBL_SIZE); + jpu_set_tbl(jpu, JCQTBL(1), qtbl_chr[quality], QTBL_SIZE); +} + +static void jpu_set_htbl(struct jpu *jpu) +{ + jpu_set_tbl(jpu, JCHTBD(0), hdctbl_lum, HDCTBL_SIZE); + jpu_set_tbl(jpu, JCHTBA(0), hactbl_lum, HACTBL_SIZE); + jpu_set_tbl(jpu, JCHTBD(1), hdctbl_chr, HDCTBL_SIZE); + jpu_set_tbl(jpu, JCHTBA(1), hactbl_chr, HACTBL_SIZE); +} + +static int jpu_wait_reset(struct jpu *jpu) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(JPU_RESET_TIMEOUT); + + while (jpu_read(jpu, JCCMD) & JCCMD_SRST) { + if (time_after(jiffies, timeout)) { + dev_err(jpu->dev, "timed out in reset\n"); + return -ETIMEDOUT; + } + schedule(); + } + + return 0; +} + +static int jpu_reset(struct jpu *jpu) +{ + jpu_write(jpu, JCCMD_SRST, JCCMD); + return jpu_wait_reset(jpu); +} + +/* + * ============================================================================ + * video ioctl operations + * ============================================================================ + */ +static void put_qtbl(u8 *p, const u8 *qtbl) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(zigzag); i++) + p[i] = *(qtbl + zigzag[i]); +} + +static void put_htbl(u8 *p, const u8 *htbl, unsigned int len) +{ + unsigned int i, j; + + for (i = 0; i < len; i += 4) + for (j = 0; j < 4 && (i + j) < len; ++j) + p[i + j] = htbl[i + 3 - j]; +} + +static void jpu_generate_hdr(unsigned short quality, unsigned char *p) +{ + put_qtbl(p + JPU_JPEG_QTBL_LUM_OFFSET, (const u8 *)qtbl_lum[quality]); + put_qtbl(p + JPU_JPEG_QTBL_CHR_OFFSET, (const u8 *)qtbl_chr[quality]); + + put_htbl(p + JPU_JPEG_HDCTBL_LUM_OFFSET, (const u8 *)hdctbl_lum, + JPU_JPEG_HDCTBL_SIZE); + put_htbl(p + JPU_JPEG_HACTBL_LUM_OFFSET, (const u8 *)hactbl_lum, + JPU_JPEG_HACTBL_SIZE); + + put_htbl(p + JPU_JPEG_HDCTBL_CHR_OFFSET, (const u8 *)hdctbl_chr, + JPU_JPEG_HDCTBL_SIZE); + put_htbl(p + JPU_JPEG_HACTBL_CHR_OFFSET, (const u8 *)hactbl_chr, + JPU_JPEG_HACTBL_SIZE); +} + +static int get_byte(struct jpeg_buffer *buf) +{ + if (buf->curr >= buf->end) + return -1; + + return *(u8 *)buf->curr++; +} + +static int get_word_be(struct jpeg_buffer *buf, unsigned int *word) +{ + if (buf->end - buf->curr < 2) + return -1; + + *word = get_unaligned_be16(buf->curr); + buf->curr += 2; + + return 0; +} + +static void skip(struct jpeg_buffer *buf, unsigned long len) +{ + buf->curr += min((unsigned long)(buf->end - buf->curr), len); +} + +static u8 jpu_parse_hdr(void *buffer, unsigned long size, unsigned int *width, + unsigned int *height) +{ + struct jpeg_buffer jpeg_buffer; + unsigned int word; + bool soi = false; + + jpeg_buffer.end = buffer + size; + jpeg_buffer.curr = buffer; + + /* + * basic size check and EOI - we don't want to let JPU cross + * buffer bounds in any case. Hope it's stopping by EOI. + */ + if (size < JPU_JPEG_MIN_SIZE || *(u8 *)(buffer + size - 1) != EOI) + return 0; + + for (;;) { + int c; + + /* skip preceding filler bytes */ + do + c = get_byte(&jpeg_buffer); + while (c == 0xff || c == 0); + + if (!soi && c == SOI) { + soi = true; + continue; + } else if (soi != (c != SOI)) + return 0; + + switch (c) { + case SOF0: /* SOF0: baseline JPEG */ + skip(&jpeg_buffer, 3); /* segment length and bpp */ + if (get_word_be(&jpeg_buffer, height) || + get_word_be(&jpeg_buffer, width) || + get_byte(&jpeg_buffer) != 3) /* YCbCr only */ + return 0; + + skip(&jpeg_buffer, 1); + return get_byte(&jpeg_buffer); + case DHT: + case DQT: + case COM: + case DRI: + case APP0 ... APP0 + 0x0f: + if (get_word_be(&jpeg_buffer, &word)) + return 0; + skip(&jpeg_buffer, (long)word - 2); + case 0: + break; + default: + return 0; + } + } + + return 0; +} + +static int jpu_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (ctx->encoder) + strlcpy(cap->card, DRV_NAME " encoder", sizeof(cap->card)); + else + strlcpy(cap->card, DRV_NAME " decoder", sizeof(cap->card)); + + strlcpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(ctx->jpu->dev)); + cap->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + cap->capabilities = V4L2_CAP_DEVICE_CAPS | cap->device_caps; + memset(cap->reserved, 0, sizeof(cap->reserved)); + + return 0; +} + +static struct jpu_fmt *jpu_find_format(bool encoder, u32 pixelformat, + unsigned int fmt_type) +{ + unsigned int i, fmt_flag; + + if (encoder) + fmt_flag = fmt_type == JPU_FMT_TYPE_OUTPUT ? JPU_ENC_OUTPUT : + JPU_ENC_CAPTURE; + else + fmt_flag = fmt_type == JPU_FMT_TYPE_OUTPUT ? JPU_DEC_OUTPUT : + JPU_DEC_CAPTURE; + + for (i = 0; i < ARRAY_SIZE(jpu_formats); i++) { + struct jpu_fmt *fmt = &jpu_formats[i]; + + if (fmt->fourcc == pixelformat && fmt->types & fmt_flag) + return fmt; + } + + return NULL; +} + +static int jpu_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + unsigned int i, num = 0; + + for (i = 0; i < ARRAY_SIZE(jpu_formats); ++i) { + if (jpu_formats[i].types & type) { + if (num == f->index) + break; + ++num; + } + } + + if (i >= ARRAY_SIZE(jpu_formats)) + return -EINVAL; + + f->pixelformat = jpu_formats[i].fourcc; + + return 0; +} + +static int jpu_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + return jpu_enum_fmt(f, ctx->encoder ? JPU_ENC_CAPTURE : + JPU_DEC_CAPTURE); +} + +static int jpu_enum_fmt_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + return jpu_enum_fmt(f, ctx->encoder ? JPU_ENC_OUTPUT : JPU_DEC_OUTPUT); +} + +static struct jpu_q_data *jpu_get_q_data(struct jpu_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + else + return &ctx->cap_q; +} + +static void jpu_bound_align_image(u32 *w, unsigned int w_min, + unsigned int w_max, unsigned int w_align, + u32 *h, unsigned int h_min, + unsigned int h_max, unsigned int h_align) +{ + unsigned int width, height, w_step, h_step; + + width = *w; + height = *h; + + w_step = 1U << w_align; + h_step = 1U << h_align; + v4l_bound_align_image(w, w_min, w_max, w_align, h, h_min, h_max, + h_align, 3); + + if (*w < width && *w + w_step < w_max) + *w += w_step; + if (*h < height && *h + h_step < h_max) + *h += h_step; +} + +static int __jpu_try_fmt(struct jpu_ctx *ctx, struct jpu_fmt **fmtinfo, + struct v4l2_pix_format_mplane *pix, + enum v4l2_buf_type type) +{ + struct jpu_fmt *fmt; + unsigned int f_type, w, h; + + f_type = V4L2_TYPE_IS_OUTPUT(type) ? JPU_FMT_TYPE_OUTPUT : + JPU_FMT_TYPE_CAPTURE; + + fmt = jpu_find_format(ctx->encoder, pix->pixelformat, f_type); + if (!fmt) { + unsigned int pixelformat; + + dev_dbg(ctx->jpu->dev, "unknown format; set default format\n"); + if (ctx->encoder) + pixelformat = f_type == JPU_FMT_TYPE_OUTPUT ? + V4L2_PIX_FMT_NV16M : V4L2_PIX_FMT_JPEG; + else + pixelformat = f_type == JPU_FMT_TYPE_CAPTURE ? + V4L2_PIX_FMT_NV16M : V4L2_PIX_FMT_JPEG; + fmt = jpu_find_format(ctx->encoder, pixelformat, f_type); + } + + pix->pixelformat = fmt->fourcc; + pix->colorspace = fmt->colorspace; + pix->field = V4L2_FIELD_NONE; + pix->num_planes = fmt->num_planes; + memset(pix->reserved, 0, sizeof(pix->reserved)); + + jpu_bound_align_image(&pix->width, JPU_WIDTH_MIN, JPU_WIDTH_MAX, + fmt->h_align, &pix->height, JPU_HEIGHT_MIN, + JPU_HEIGHT_MAX, fmt->v_align); + + w = pix->width; + h = pix->height; + + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* ignore userspaces's sizeimage for encoding */ + if (pix->plane_fmt[0].sizeimage <= 0 || ctx->encoder) + pix->plane_fmt[0].sizeimage = JPU_JPEG_HDR_SIZE + + (JPU_JPEG_MAX_BYTES_PER_PIXEL * w * h); + pix->plane_fmt[0].bytesperline = 0; + memset(pix->plane_fmt[0].reserved, 0, + sizeof(pix->plane_fmt[0].reserved)); + } else { + unsigned int i, bpl = 0; + + for (i = 0; i < pix->num_planes; ++i) + bpl = max(bpl, pix->plane_fmt[i].bytesperline); + + bpl = clamp_t(unsigned int, bpl, w, JPU_WIDTH_MAX); + bpl = round_up(bpl, JPU_MEMALIGN); + + for (i = 0; i < pix->num_planes; ++i) { + pix->plane_fmt[i].bytesperline = bpl; + pix->plane_fmt[i].sizeimage = bpl * h * fmt->bpp[i] / 8; + memset(pix->plane_fmt[i].reserved, 0, + sizeof(pix->plane_fmt[i].reserved)); + } + } + + if (fmtinfo) + *fmtinfo = fmt; + + return 0; +} + +static int jpu_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (!v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type)) + return -EINVAL; + + return __jpu_try_fmt(ctx, NULL, &f->fmt.pix_mp, f->type); +} + +static int jpu_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct jpu_ctx *ctx = fh_to_ctx(priv); + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + struct jpu_fmt *fmtinfo; + struct jpu_q_data *q_data; + int ret; + + vq = v4l2_m2m_get_vq(m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->jpu->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = __jpu_try_fmt(ctx, &fmtinfo, &f->fmt.pix_mp, f->type); + if (ret < 0) + return ret; + + q_data = jpu_get_q_data(ctx, f->type); + + q_data->format = f->fmt.pix_mp; + q_data->fmtinfo = fmtinfo; + + return 0; +} + +static int jpu_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct jpu_q_data *q_data; + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (!v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type)) + return -EINVAL; + + q_data = jpu_get_q_data(ctx, f->type); + f->fmt.pix_mp = q_data->format; + + return 0; +} + +/* + * V4L2 controls + */ +static int jpu_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct jpu_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + + spin_lock_irqsave(&ctx->jpu->lock, flags); + if (ctrl->id == V4L2_CID_JPEG_COMPRESSION_QUALITY) + ctx->compr_quality = ctrl->val; + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + + return 0; +} + +static const struct v4l2_ctrl_ops jpu_ctrl_ops = { + .s_ctrl = jpu_s_ctrl, +}; + +static int jpu_streamon(struct file *file, void *priv, enum v4l2_buf_type type) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + struct jpu_q_data *src_q_data, *dst_q_data, *orig, adj, *ref; + enum v4l2_buf_type adj_type; + + src_q_data = jpu_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + dst_q_data = jpu_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + if (ctx->encoder) { + adj = *src_q_data; + orig = src_q_data; + ref = dst_q_data; + adj_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + } else { + adj = *dst_q_data; + orig = dst_q_data; + ref = src_q_data; + adj_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + } + + adj.format.width = ref->format.width; + adj.format.height = ref->format.height; + + __jpu_try_fmt(ctx, NULL, &adj.format, adj_type); + + if (adj.format.width != orig->format.width || + adj.format.height != orig->format.height) { + dev_err(ctx->jpu->dev, "src and dst formats do not match.\n"); + /* maybe we can return -EPIPE here? */ + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops jpu_ioctl_ops = { + .vidioc_querycap = jpu_querycap, + + .vidioc_enum_fmt_vid_cap_mplane = jpu_enum_fmt_cap, + .vidioc_enum_fmt_vid_out_mplane = jpu_enum_fmt_out, + .vidioc_g_fmt_vid_cap_mplane = jpu_g_fmt, + .vidioc_g_fmt_vid_out_mplane = jpu_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = jpu_try_fmt, + .vidioc_try_fmt_vid_out_mplane = jpu_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = jpu_s_fmt, + .vidioc_s_fmt_vid_out_mplane = jpu_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = jpu_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe +}; + +static int jpu_controls_create(struct jpu_ctx *ctx) +{ + struct v4l2_ctrl *ctrl; + int ret; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 1); + + ctrl = v4l2_ctrl_new_std(&ctx->ctrl_handler, &jpu_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, + 0, JPU_MAX_QUALITY - 1, 1, 0); + + if (ctx->ctrl_handler.error) { + ret = ctx->ctrl_handler.error; + goto error_free; + } + + if (!ctx->encoder) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY; + + ret = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (ret < 0) + goto error_free; + + return 0; + +error_free: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ret; +} + +/* + * ============================================================================ + * Queue operations + * ============================================================================ + */ +static int jpu_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vq); + struct jpu_q_data *q_data; + unsigned int i; + + q_data = jpu_get_q_data(ctx, vq->type); + + *nplanes = q_data->format.num_planes; + + for (i = 0; i < *nplanes; i++) { + unsigned int q_size = q_data->format.plane_fmt[i].sizeimage; + unsigned int f_size = fmt ? + fmt->fmt.pix_mp.plane_fmt[i].sizeimage : 0; + + if (fmt && f_size < q_size) + return -EINVAL; + + sizes[i] = fmt ? f_size : q_size; + alloc_ctxs[i] = ctx->jpu->alloc_ctx; + } + + return 0; +} + +static int jpu_buf_prepare(struct vb2_buffer *vb) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct jpu_q_data *q_data; + unsigned int i; + + q_data = jpu_get_q_data(ctx, vb->vb2_queue->type); + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vb->v4l2_buf.field == V4L2_FIELD_ANY) + vb->v4l2_buf.field = V4L2_FIELD_NONE; + if (vb->v4l2_buf.field != V4L2_FIELD_NONE) { + dev_err(ctx->jpu->dev, "%s field isn't supported\n", + __func__); + return -EINVAL; + } + } + + for (i = 0; i < q_data->format.num_planes; i++) { + unsigned long size = q_data->format.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dev_err(ctx->jpu->dev, + "%s: data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, i), size); + return -EINVAL; + } + + /* decoder capture queue */ + if (!ctx->encoder && !V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void jpu_buf_queue(struct vb2_buffer *vb) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + if (!ctx->encoder && V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + struct jpu_buffer *jpu_buf = vb2_to_jpu_buffer(vb); + struct jpu_q_data *q_data, adjust; + void *buffer = vb2_plane_vaddr(vb, 0); + unsigned long buf_size = vb2_get_plane_payload(vb, 0); + unsigned int width, height; + + u8 subsampling = jpu_parse_hdr(buffer, buf_size, &width, + &height); + + /* check if JPEG data basic parsing was successful */ + if (subsampling != JPU_JPEG_422 && subsampling != JPU_JPEG_420) + goto format_error; + + q_data = &ctx->out_q; + + adjust = *q_data; + adjust.format.width = width; + adjust.format.height = height; + + __jpu_try_fmt(ctx, &adjust.fmtinfo, &adjust.format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + if (adjust.format.width != q_data->format.width || + adjust.format.height != q_data->format.height) + goto format_error; + + /* + * keep subsampling in buffer to check it + * for compatibility in device_run + */ + jpu_buf->subsampling = subsampling; + } + + if (ctx->fh.m2m_ctx) + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); + + return; + +format_error: + dev_err(ctx->jpu->dev, "incompatible or corrupted JPEG data\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +static void jpu_buf_finish(struct vb2_buffer *vb) +{ + struct jpu_buffer *jpu_buf = vb2_to_jpu_buffer(vb); + struct jpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct jpu_q_data *q_data = &ctx->out_q; + enum v4l2_buf_type type = vb->vb2_queue->type; + u8 *buffer; + + if (vb->state == VB2_BUF_STATE_DONE) + vb->v4l2_buf.sequence = jpu_get_q_data(ctx, type)->sequence++; + + if (!ctx->encoder || vb->state != VB2_BUF_STATE_DONE || + V4L2_TYPE_IS_OUTPUT(type)) + return; + + buffer = vb2_plane_vaddr(vb, 0); + + memcpy(buffer, jpeg_hdrs[jpu_buf->compr_quality], JPU_JPEG_HDR_SIZE); + *(u16 *)(buffer + JPU_JPEG_HEIGHT_OFFSET) = + cpu_to_be16(q_data->format.height); + *(u16 *)(buffer + JPU_JPEG_WIDTH_OFFSET) = + cpu_to_be16(q_data->format.width); + *(buffer + JPU_JPEG_SUBS_OFFSET) = q_data->fmtinfo->subsampling; +} + +static int jpu_start_streaming(struct vb2_queue *vq, unsigned count) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vq); + struct jpu_q_data *q_data = jpu_get_q_data(ctx, vq->type); + + q_data->sequence = 0; + return 0; +} + +static void jpu_stop_streaming(struct vb2_queue *vq) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_buffer *vb; + unsigned long flags; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vb == NULL) + return; + spin_lock_irqsave(&ctx->jpu->lock, flags); + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + } +} + +static struct vb2_ops jpu_qops = { + .queue_setup = jpu_queue_setup, + .buf_prepare = jpu_buf_prepare, + .buf_queue = jpu_buf_queue, + .buf_finish = jpu_buf_finish, + .start_streaming = jpu_start_streaming, + .stop_streaming = jpu_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int jpu_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct jpu_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct jpu_buffer); + src_vq->ops = &jpu_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->jpu->mutex; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct jpu_buffer); + dst_vq->ops = &jpu_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->jpu->mutex; + + return vb2_queue_init(dst_vq); +} + +/* + * ============================================================================ + * Device file operations + * ============================================================================ + */ +static int jpu_open(struct file *file) +{ + struct jpu *jpu = video_drvdata(file); + struct video_device *vfd = video_devdata(file); + struct jpu_ctx *ctx; + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, vfd); + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->jpu = jpu; + ctx->encoder = vfd == &jpu->vfd_encoder; + + __jpu_try_fmt(ctx, &ctx->out_q.fmtinfo, &ctx->out_q.format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + __jpu_try_fmt(ctx, &ctx->cap_q.fmtinfo, &ctx->cap_q.format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(jpu->m2m_dev, ctx, jpu_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto v4l_prepare_rollback; + } + + ret = jpu_controls_create(ctx); + if (ret < 0) + goto v4l_prepare_rollback; + + if (mutex_lock_interruptible(&jpu->mutex)) { + ret = -ERESTARTSYS; + goto v4l_prepare_rollback; + } + + if (jpu->ref_count == 0) { + ret = clk_prepare_enable(jpu->clk); + if (ret < 0) + goto device_prepare_rollback; + /* ...issue software reset */ + ret = jpu_reset(jpu); + if (ret) + goto device_prepare_rollback; + } + + jpu->ref_count++; + + mutex_unlock(&jpu->mutex); + return 0; + +device_prepare_rollback: + mutex_unlock(&jpu->mutex); +v4l_prepare_rollback: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + return ret; +} + +static int jpu_release(struct file *file) +{ + struct jpu *jpu = video_drvdata(file); + struct jpu_ctx *ctx = fh_to_ctx(file->private_data); + + mutex_lock(&jpu->mutex); + if (--jpu->ref_count == 0) + clk_disable_unprepare(jpu->clk); + mutex_unlock(&jpu->mutex); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations jpu_fops = { + .owner = THIS_MODULE, + .open = jpu_open, + .release = jpu_release, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +/* + * ============================================================================ + * mem2mem callbacks + * ============================================================================ + */ +static void jpu_cleanup(struct jpu_ctx *ctx, bool reset) +{ + /* remove current buffers and finish job */ + struct vb2_buffer *src_buf, *dst_buf; + unsigned long flags; + + spin_lock_irqsave(&ctx->jpu->lock, flags); + + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + + /* ...and give it a chance on next run */ + if (reset) + jpu_write(ctx->jpu, JCCMD_SRST, JCCMD); + + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + + v4l2_m2m_job_finish(ctx->jpu->m2m_dev, ctx->fh.m2m_ctx); +} + +static void jpu_device_run(void *priv) +{ + struct jpu_ctx *ctx = priv; + struct jpu *jpu = ctx->jpu; + struct jpu_buffer *jpu_buf; + struct jpu_q_data *q_data; + struct vb2_buffer *src_buf, *dst_buf; + unsigned int w, h, bpl; + unsigned char num_planes, subsampling; + unsigned long flags; + + /* ...wait until module reset completes; we have mutex locked here */ + if (jpu_wait_reset(jpu)) { + jpu_cleanup(ctx, true); + return; + } + + spin_lock_irqsave(&ctx->jpu->lock, flags); + + jpu->curr = ctx; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + if (ctx->encoder) { + jpu_buf = vb2_to_jpu_buffer(dst_buf); + q_data = &ctx->out_q; + } else { + jpu_buf = vb2_to_jpu_buffer(src_buf); + q_data = &ctx->cap_q; + } + + w = q_data->format.width; + h = q_data->format.height; + bpl = q_data->format.plane_fmt[0].bytesperline; + num_planes = q_data->fmtinfo->num_planes; + subsampling = q_data->fmtinfo->subsampling; + + if (ctx->encoder) { + unsigned long src_1_addr, src_2_addr, dst_addr; + unsigned int redu, inft; + + dst_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + src_1_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); + if (num_planes > 1) + src_2_addr = vb2_dma_contig_plane_dma_addr(src_buf, 1); + else + src_2_addr = src_1_addr + w * h; + + jpu_buf->compr_quality = ctx->compr_quality; + + if (subsampling == JPU_JPEG_420) { + redu = JCMOD_REDU_420; + inft = JIFECNT_INFT_420; + } else { + redu = JCMOD_REDU_422; + inft = JIFECNT_INFT_422; + } + + /* only no marker mode works for encoding */ + jpu_write(jpu, JCMOD_DSP_ENC | JCMOD_PCTR | redu | + JCMOD_MSKIP_ENABLE, JCMOD); + + jpu_write(jpu, JIFECNT_SWAP_WB | inft, JIFECNT); + jpu_write(jpu, JIFDCNT_SWAP_WB, JIFDCNT); + jpu_write(jpu, JINTE_TRANSF_COMPL, JINTE); + + /* Y and C components source addresses */ + jpu_write(jpu, src_1_addr, JIFESYA1); + jpu_write(jpu, src_2_addr, JIFESCA1); + + /* memory width */ + jpu_write(jpu, bpl, JIFESMW); + + jpu_write(jpu, (w >> 8) & JCSZ_MASK, JCHSZU); + jpu_write(jpu, w & JCSZ_MASK, JCHSZD); + + jpu_write(jpu, (h >> 8) & JCSZ_MASK, JCVSZU); + jpu_write(jpu, h & JCSZ_MASK, JCVSZD); + + jpu_write(jpu, w, JIFESHSZ); + jpu_write(jpu, h, JIFESVSZ); + + jpu_write(jpu, dst_addr + JPU_JPEG_HDR_SIZE, JIFEDA1); + + jpu_write(jpu, 0 << JCQTN_SHIFT(1) | 1 << JCQTN_SHIFT(2) | + 1 << JCQTN_SHIFT(3), JCQTN); + + jpu_write(jpu, 0 << JCHTN_AC_SHIFT(1) | 0 << JCHTN_DC_SHIFT(1) | + 1 << JCHTN_AC_SHIFT(2) | 1 << JCHTN_DC_SHIFT(2) | + 1 << JCHTN_AC_SHIFT(3) | 1 << JCHTN_DC_SHIFT(3), + JCHTN); + + jpu_set_qtbl(jpu, ctx->compr_quality); + jpu_set_htbl(jpu); + } else { + unsigned long src_addr, dst_1_addr, dst_2_addr; + + if (jpu_buf->subsampling != subsampling) { + dev_err(ctx->jpu->dev, + "src and dst formats do not match.\n"); + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + jpu_cleanup(ctx, false); + return; + } + + src_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); + dst_1_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + if (q_data->fmtinfo->num_planes > 1) + dst_2_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 1); + else + dst_2_addr = dst_1_addr + w * h; + + /* ...set up decoder operation */ + jpu_write(jpu, JCMOD_DSP_DEC | JCMOD_PCTR, JCMOD); + jpu_write(jpu, JIFECNT_SWAP_WB, JIFECNT); + jpu_write(jpu, JIFDCNT_SWAP_WB, JIFDCNT); + + /* ...enable interrupts on transfer completion and d-g error */ + jpu_write(jpu, JINTE_TRANSF_COMPL | JINTE_ERR, JINTE); + + /* ...set source/destination addresses of encoded data */ + jpu_write(jpu, src_addr, JIFDSA1); + jpu_write(jpu, dst_1_addr, JIFDDYA1); + jpu_write(jpu, dst_2_addr, JIFDDCA1); + + jpu_write(jpu, bpl, JIFDDMW); + } + + /* ...start encoder/decoder operation */ + jpu_write(jpu, JCCMD_JSRT, JCCMD); + + spin_unlock_irqrestore(&ctx->jpu->lock, flags); +} + +static int jpu_job_ready(void *priv) +{ + return 1; +} + +static void jpu_job_abort(void *priv) +{ + struct jpu_ctx *ctx = priv; + + if (!wait_event_timeout(ctx->jpu->irq_queue, !ctx->jpu->curr, + msecs_to_jiffies(JPU_JOB_TIMEOUT))) + jpu_cleanup(ctx, true); +} + +static struct v4l2_m2m_ops jpu_m2m_ops = { + .device_run = jpu_device_run, + .job_ready = jpu_job_ready, + .job_abort = jpu_job_abort, +}; + +/* + * ============================================================================ + * IRQ handler + * ============================================================================ + */ +static irqreturn_t jpu_irq_handler(int irq, void *dev_id) +{ + struct jpu *jpu = dev_id; + struct jpu_ctx *curr_ctx; + struct vb2_buffer *src_buf, *dst_buf; + unsigned int int_status; + + int_status = jpu_read(jpu, JINTS); + + /* ...spurious interrupt */ + if (!((JINTS_TRANSF_COMPL | JINTS_PROCESS_COMPL | JINTS_ERR) & + int_status)) + return IRQ_NONE; + + /* ...clear interrupts */ + jpu_write(jpu, ~(int_status & JINTS_MASK), JINTS); + if (int_status & (JINTS_ERR | JINTS_PROCESS_COMPL)) + jpu_write(jpu, JCCMD_JEND, JCCMD); + + spin_lock(&jpu->lock); + + if ((int_status & JINTS_PROCESS_COMPL) && + !(int_status & JINTS_TRANSF_COMPL)) + goto handled; + + curr_ctx = v4l2_m2m_get_curr_priv(jpu->m2m_dev); + if (!curr_ctx) { + /* ...instance is not running */ + dev_err(jpu->dev, "no active context for m2m\n"); + goto handled; + } + + src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + if (int_status & JINTS_TRANSF_COMPL) { + if (curr_ctx->encoder) { + unsigned long payload_size = jpu_read(jpu, JCDTCU) << 16 + | jpu_read(jpu, JCDTCM) << 8 + | jpu_read(jpu, JCDTCD); + vb2_set_plane_payload(dst_buf, 0, + payload_size + JPU_JPEG_HDR_SIZE); + } + + dst_buf->v4l2_buf.field = src_buf->v4l2_buf.field; + dst_buf->v4l2_buf.timestamp = src_buf->v4l2_buf.timestamp; + if (src_buf->v4l2_buf.flags & V4L2_BUF_FLAG_TIMECODE) + dst_buf->v4l2_buf.timecode = src_buf->v4l2_buf.timecode; + dst_buf->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_buf->v4l2_buf.flags |= src_buf->v4l2_buf.flags & + V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_buf->v4l2_buf.flags = src_buf->v4l2_buf.flags & + (V4L2_BUF_FLAG_TIMECODE | V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + } else if (int_status & JINTS_ERR) { + unsigned char error = jpu_read(jpu, JCDERR) & JCDERR_MASK; + + dev_dbg(jpu->dev, "processing error: %#X: %s\n", error, + error_to_text[error]); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + } + + jpu->curr = NULL; + + /* ...reset JPU after completion */ + jpu_write(jpu, JCCMD_SRST, JCCMD); + spin_unlock(&jpu->lock); + + v4l2_m2m_job_finish(jpu->m2m_dev, curr_ctx->fh.m2m_ctx); + + /* ...wakeup abort routine if needed */ + wake_up(&jpu->irq_queue); + + return IRQ_HANDLED; + +handled: + spin_unlock(&jpu->lock); + return IRQ_HANDLED; +} + +/* + * ============================================================================ + * Driver basic infrastructure + * ============================================================================ + */ +static const struct of_device_id jpu_dt_ids[] = { + { .compatible = "renesas,jpu-r8a7790" }, /* H2 */ + { .compatible = "renesas,jpu-r8a7791" }, /* M2-W */ + { .compatible = "renesas,jpu-r8a7792" }, /* V2H */ + { .compatible = "renesas,jpu-r8a7793" }, /* M2-N */ + { }, +}; +MODULE_DEVICE_TABLE(of, jpu_dt_ids); + +static int jpu_probe(struct platform_device *pdev) +{ + struct jpu *jpu; + struct resource *res; + int ret; + unsigned int i; + + jpu = devm_kzalloc(&pdev->dev, sizeof(*jpu), GFP_KERNEL); + if (!jpu) + return -ENOMEM; + + init_waitqueue_head(&jpu->irq_queue); + mutex_init(&jpu->mutex); + spin_lock_init(&jpu->lock); + jpu->dev = &pdev->dev; + + /* memory-mapped registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + jpu->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(jpu->regs)) + return PTR_ERR(jpu->regs); + + /* interrupt service routine registration */ + jpu->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "cannot find IRQ\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, jpu->irq, jpu_irq_handler, 0, + dev_name(&pdev->dev), jpu); + if (ret) { + dev_err(&pdev->dev, "cannot claim IRQ %d\n", jpu->irq); + return ret; + } + + /* clocks */ + jpu->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(jpu->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(jpu->clk); + } + + /* v4l2 device */ + ret = v4l2_device_register(&pdev->dev, &jpu->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + return ret; + } + + /* mem2mem device */ + jpu->m2m_dev = v4l2_m2m_init(&jpu_m2m_ops); + if (IS_ERR(jpu->m2m_dev)) { + v4l2_err(&jpu->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(jpu->m2m_dev); + goto device_register_rollback; + } + + jpu->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(jpu->alloc_ctx)) { + v4l2_err(&jpu->v4l2_dev, "Failed to init memory allocator\n"); + ret = PTR_ERR(jpu->alloc_ctx); + goto m2m_init_rollback; + } + + /* fill in qantization and Huffman tables for encoder */ + for (i = 0; i < JPU_MAX_QUALITY; i++) + jpu_generate_hdr(i, (unsigned char *)jpeg_hdrs[i]); + + strlcpy(jpu->vfd_encoder.name, DRV_NAME, sizeof(jpu->vfd_encoder.name)); + jpu->vfd_encoder.fops = &jpu_fops; + jpu->vfd_encoder.ioctl_ops = &jpu_ioctl_ops; + jpu->vfd_encoder.minor = -1; + jpu->vfd_encoder.release = video_device_release_empty; + jpu->vfd_encoder.lock = &jpu->mutex; + jpu->vfd_encoder.v4l2_dev = &jpu->v4l2_dev; + jpu->vfd_encoder.vfl_dir = VFL_DIR_M2M; + + ret = video_register_device(&jpu->vfd_encoder, VFL_TYPE_GRABBER, -1); + if (ret) { + v4l2_err(&jpu->v4l2_dev, "Failed to register video device\n"); + goto vb2_allocator_rollback; + } + + video_set_drvdata(&jpu->vfd_encoder, jpu); + + strlcpy(jpu->vfd_decoder.name, DRV_NAME, sizeof(jpu->vfd_decoder.name)); + jpu->vfd_decoder.fops = &jpu_fops; + jpu->vfd_decoder.ioctl_ops = &jpu_ioctl_ops; + jpu->vfd_decoder.minor = -1; + jpu->vfd_decoder.release = video_device_release_empty; + jpu->vfd_decoder.lock = &jpu->mutex; + jpu->vfd_decoder.v4l2_dev = &jpu->v4l2_dev; + jpu->vfd_decoder.vfl_dir = VFL_DIR_M2M; + + ret = video_register_device(&jpu->vfd_decoder, VFL_TYPE_GRABBER, -1); + if (ret) { + v4l2_err(&jpu->v4l2_dev, "Failed to register video device\n"); + goto enc_vdev_register_rollback; + } + + video_set_drvdata(&jpu->vfd_decoder, jpu); + platform_set_drvdata(pdev, jpu); + + v4l2_info(&jpu->v4l2_dev, "encoder device registered as /dev/video%d\n", + jpu->vfd_encoder.num); + v4l2_info(&jpu->v4l2_dev, "decoder device registered as /dev/video%d\n", + jpu->vfd_decoder.num); + + return 0; + +enc_vdev_register_rollback: + video_unregister_device(&jpu->vfd_encoder); + +vb2_allocator_rollback: + vb2_dma_contig_cleanup_ctx(jpu->alloc_ctx); + +m2m_init_rollback: + v4l2_m2m_release(jpu->m2m_dev); + +device_register_rollback: + v4l2_device_unregister(&jpu->v4l2_dev); + + return ret; +} + +static int jpu_remove(struct platform_device *pdev) +{ + struct jpu *jpu = platform_get_drvdata(pdev); + + video_unregister_device(&jpu->vfd_decoder); + video_unregister_device(&jpu->vfd_encoder); + vb2_dma_contig_cleanup_ctx(jpu->alloc_ctx); + v4l2_m2m_release(jpu->m2m_dev); + v4l2_device_unregister(&jpu->v4l2_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int jpu_suspend(struct device *dev) +{ + struct jpu *jpu = dev_get_drvdata(dev); + + if (jpu->ref_count == 0) + return 0; + + clk_disable_unprepare(jpu->clk); + + return 0; +} + +static int jpu_resume(struct device *dev) +{ + struct jpu *jpu = dev_get_drvdata(dev); + + if (jpu->ref_count == 0) + return 0; + + clk_prepare_enable(jpu->clk); + + return 0; +} +#endif + +static const struct dev_pm_ops jpu_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(jpu_suspend, jpu_resume) +}; + +static struct platform_driver jpu_driver = { + .probe = jpu_probe, + .remove = jpu_remove, + .driver = { + .of_match_table = jpu_dt_ids, + .name = DRV_NAME, + .pm = &jpu_pm_ops, + }, +}; + +module_platform_driver(jpu_driver); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_AUTHOR("Mikhail Ulianov <mikhail.ulyanov@cogentembedded.com>"); +MODULE_DESCRIPTION("Renesas R-Car JPEG processing unit driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/s5p-jpeg/jpeg-core.c b/drivers/media/platform/s5p-jpeg/jpeg-core.c index bfbf15756..9690f9dcb 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-core.c +++ b/drivers/media/platform/s5p-jpeg/jpeg-core.c @@ -2544,7 +2544,8 @@ static int s5p_jpeg_probe(struct platform_device *pdev) ret = video_register_device(jpeg->vfd_encoder, VFL_TYPE_GRABBER, -1); if (ret) { v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n"); - goto enc_vdev_alloc_rollback; + video_device_release(jpeg->vfd_encoder); + goto vb2_allocator_rollback; } video_set_drvdata(jpeg->vfd_encoder, jpeg); @@ -2572,7 +2573,8 @@ static int s5p_jpeg_probe(struct platform_device *pdev) ret = video_register_device(jpeg->vfd_decoder, VFL_TYPE_GRABBER, -1); if (ret) { v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n"); - goto dec_vdev_alloc_rollback; + video_device_release(jpeg->vfd_decoder); + goto enc_vdev_register_rollback; } video_set_drvdata(jpeg->vfd_decoder, jpeg); @@ -2589,15 +2591,9 @@ static int s5p_jpeg_probe(struct platform_device *pdev) return 0; -dec_vdev_alloc_rollback: - video_device_release(jpeg->vfd_decoder); - enc_vdev_register_rollback: video_unregister_device(jpeg->vfd_encoder); -enc_vdev_alloc_rollback: - video_device_release(jpeg->vfd_encoder); - vb2_allocator_rollback: vb2_dma_contig_cleanup_ctx(jpeg->alloc_ctx); @@ -2622,9 +2618,7 @@ static int s5p_jpeg_remove(struct platform_device *pdev) pm_runtime_disable(jpeg->dev); video_unregister_device(jpeg->vfd_decoder); - video_device_release(jpeg->vfd_decoder); video_unregister_device(jpeg->vfd_encoder); - video_device_release(jpeg->vfd_encoder); vb2_dma_contig_cleanup_ctx(jpeg->alloc_ctx); v4l2_m2m_release(jpeg->m2m_dev); v4l2_device_unregister(&jpeg->v4l2_dev); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c b/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c index f17609669..b1b149151 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_cmd_v6.c @@ -37,8 +37,12 @@ static int s5p_mfc_sys_init_cmd_v6(struct s5p_mfc_dev *dev) { struct s5p_mfc_cmd_args h2r_args; struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv; + int ret; + + ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_dev_context_buffer, dev); + if (ret) + return ret; - s5p_mfc_hw_call(dev->mfc_ops, alloc_dev_context_buffer, dev); mfc_write(dev, dev->ctx_buf.dma, S5P_FIMV_CONTEXT_MEM_ADDR_V6); mfc_write(dev, buf_size->dev_ctx, S5P_FIMV_CONTEXT_MEM_SIZE_V6); return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_SYS_INIT_V6, diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c index e65993f4b..2e57e9f45 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c @@ -1819,11 +1819,12 @@ static int s5p_mfc_queue_setup(struct vb2_queue *vq, struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); struct s5p_mfc_dev *dev = ctx->dev; - if (ctx->state != MFCINST_GOT_INST) { - mfc_err("inavlid state: %d\n", ctx->state); - return -EINVAL; - } if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (ctx->state != MFCINST_GOT_INST) { + mfc_err("inavlid state: %d\n", ctx->state); + return -EINVAL; + } + if (ctx->dst_fmt) *plane_count = ctx->dst_fmt->num_planes; else diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c index 00a1d8b2a..1e7250260 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr.c @@ -37,10 +37,9 @@ void s5p_mfc_init_regs(struct s5p_mfc_dev *dev) dev->mfc_regs = s5p_mfc_init_regs_v6_plus(dev); } -int s5p_mfc_alloc_priv_buf(struct device *dev, +int s5p_mfc_alloc_priv_buf(struct device *dev, dma_addr_t base, struct s5p_mfc_priv_buf *b) { - mfc_debug(3, "Allocating priv: %zu\n", b->size); b->virt = dma_alloc_coherent(dev, b->size, &b->dma, GFP_KERNEL); @@ -50,6 +49,14 @@ int s5p_mfc_alloc_priv_buf(struct device *dev, return -ENOMEM; } + if (b->dma < base) { + mfc_err("Invaling memory configuration!\n"); + mfc_err("Allocated buffer (%pad) is lower than memory base address (%pad)\n", + &b->dma, &base); + dma_free_coherent(dev, b->size, b->virt, b->dma); + return -ENOMEM; + } + mfc_debug(3, "Allocated addr %p %pad\n", b->virt, &b->dma); return 0; } diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr.h b/drivers/media/platform/s5p-mfc/s5p_mfc_opr.h index 22dfb3eff..77a08b19b 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr.h +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr.h @@ -334,7 +334,7 @@ struct s5p_mfc_hw_ops { void s5p_mfc_init_hw_ops(struct s5p_mfc_dev *dev); void s5p_mfc_init_regs(struct s5p_mfc_dev *dev); -int s5p_mfc_alloc_priv_buf(struct device *dev, +int s5p_mfc_alloc_priv_buf(struct device *dev, dma_addr_t base, struct s5p_mfc_priv_buf *b); void s5p_mfc_release_priv_buf(struct device *dev, struct s5p_mfc_priv_buf *b); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c index 9a923b1a9..6402f76cc 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c @@ -41,7 +41,7 @@ static int s5p_mfc_alloc_dec_temp_buffers_v5(struct s5p_mfc_ctx *ctx) int ret; ctx->dsc.size = buf_size->dsc; - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &ctx->dsc); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, &ctx->dsc); if (ret) { mfc_err("Failed to allocate temporary buffer\n"); return ret; @@ -172,7 +172,8 @@ static int s5p_mfc_alloc_codec_buffers_v5(struct s5p_mfc_ctx *ctx) /* Allocate only if memory from bank 1 is necessary */ if (ctx->bank1.size > 0) { - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &ctx->bank1); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, + &ctx->bank1); if (ret) { mfc_err("Failed to allocate Bank1 temporary buffer\n"); return ret; @@ -181,7 +182,8 @@ static int s5p_mfc_alloc_codec_buffers_v5(struct s5p_mfc_ctx *ctx) } /* Allocate only if memory from bank 2 is necessary */ if (ctx->bank2.size > 0) { - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_r, &ctx->bank2); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_r, dev->bank2, + &ctx->bank2); if (ret) { mfc_err("Failed to allocate Bank2 temporary buffer\n"); s5p_mfc_release_priv_buf(ctx->dev->mem_dev_l, &ctx->bank1); @@ -212,7 +214,7 @@ static int s5p_mfc_alloc_instance_buffer_v5(struct s5p_mfc_ctx *ctx) else ctx->ctx.size = buf_size->non_h264_ctx; - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &ctx->ctx); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, &ctx->ctx); if (ret) { mfc_err("Failed to allocate instance buffer\n"); return ret; @@ -225,7 +227,7 @@ static int s5p_mfc_alloc_instance_buffer_v5(struct s5p_mfc_ctx *ctx) /* Initialize shared memory */ ctx->shm.size = buf_size->shm; - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &ctx->shm); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, &ctx->shm); if (ret) { mfc_err("Failed to allocate shared memory buffer\n"); s5p_mfc_release_priv_buf(dev->mem_dev_l, &ctx->ctx); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c index 12497f5ed..e5cb30e1f 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c @@ -239,7 +239,8 @@ static int s5p_mfc_alloc_codec_buffers_v6(struct s5p_mfc_ctx *ctx) /* Allocate only if memory from bank 1 is necessary */ if (ctx->bank1.size > 0) { - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &ctx->bank1); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, + &ctx->bank1); if (ret) { mfc_err("Failed to allocate Bank1 memory\n"); return ret; @@ -291,7 +292,7 @@ static int s5p_mfc_alloc_instance_buffer_v6(struct s5p_mfc_ctx *ctx) break; } - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &ctx->ctx); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, &ctx->ctx); if (ret) { mfc_err("Failed to allocate instance buffer\n"); return ret; @@ -320,7 +321,8 @@ static int s5p_mfc_alloc_dev_context_buffer_v6(struct s5p_mfc_dev *dev) mfc_debug_enter(); dev->ctx_buf.size = buf_size->dev_ctx; - ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, &dev->ctx_buf); + ret = s5p_mfc_alloc_priv_buf(dev->mem_dev_l, dev->bank1, + &dev->ctx_buf); if (ret) { mfc_err("Failed to allocate device context buffer\n"); return ret; @@ -1734,7 +1736,7 @@ static void s5p_mfc_try_run_v6(struct s5p_mfc_dev *dev) mfc_debug(1, "New context: %d\n", new_ctx); ctx = dev->ctx[new_ctx]; - mfc_debug(1, "Seting new context to %p\n", ctx); + mfc_debug(1, "Setting new context to %p\n", ctx); /* Got context to run in ctx */ mfc_debug(1, "ctx->dst_queue_cnt=%d ctx->dpb_count=%d ctx->src_queue_cnt=%d\n", ctx->dst_queue_cnt, ctx->pb_count, ctx->src_queue_cnt); diff --git a/drivers/media/platform/s5p-tv/hdmiphy_drv.c b/drivers/media/platform/s5p-tv/hdmiphy_drv.c index c2f2e3564..aae652351 100644 --- a/drivers/media/platform/s5p-tv/hdmiphy_drv.c +++ b/drivers/media/platform/s5p-tv/hdmiphy_drv.c @@ -315,7 +315,6 @@ MODULE_DEVICE_TABLE(i2c, hdmiphy_id); static struct i2c_driver hdmiphy_driver = { .driver = { .name = "s5p-hdmiphy", - .owner = THIS_MODULE, }, .probe = hdmiphy_probe, .remove = hdmiphy_remove, diff --git a/drivers/media/platform/s5p-tv/mixer_reg.c b/drivers/media/platform/s5p-tv/mixer_reg.c index b71340302..5127acb1e 100644 --- a/drivers/media/platform/s5p-tv/mixer_reg.c +++ b/drivers/media/platform/s5p-tv/mixer_reg.c @@ -357,17 +357,15 @@ void mxr_reg_streamoff(struct mxr_device *mdev) int mxr_reg_wait4vsync(struct mxr_device *mdev) { - int ret; + long time_left; clear_bit(MXR_EVENT_VSYNC, &mdev->event_flags); /* TODO: consider adding interruptible */ - ret = wait_event_timeout(mdev->event_queue, - test_bit(MXR_EVENT_VSYNC, &mdev->event_flags), - msecs_to_jiffies(1000)); - if (ret > 0) + time_left = wait_event_timeout(mdev->event_queue, + test_bit(MXR_EVENT_VSYNC, &mdev->event_flags), + msecs_to_jiffies(1000)); + if (time_left > 0) return 0; - if (ret < 0) - return ret; mxr_warn(mdev, "no vsync detected - timeout\n"); return -ETIME; } diff --git a/drivers/media/platform/s5p-tv/sii9234_drv.c b/drivers/media/platform/s5p-tv/sii9234_drv.c index db8c17bb4..8d171310a 100644 --- a/drivers/media/platform/s5p-tv/sii9234_drv.c +++ b/drivers/media/platform/s5p-tv/sii9234_drv.c @@ -397,7 +397,6 @@ MODULE_DEVICE_TABLE(i2c, sii9234_id); static struct i2c_driver sii9234_driver = { .driver = { .name = "sii9234", - .owner = THIS_MODULE, .pm = &sii9234_pm_ops, }, .probe = sii9234_probe, diff --git a/drivers/media/platform/sh_veu.c b/drivers/media/platform/sh_veu.c index 2554f3719..f5e3eb3a2 100644 --- a/drivers/media/platform/sh_veu.c +++ b/drivers/media/platform/sh_veu.c @@ -211,7 +211,7 @@ static enum v4l2_colorspace sh_veu_4cc2cspace(u32 fourcc) case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV24: - return V4L2_COLORSPACE_JPEG; + return V4L2_COLORSPACE_SMPTE170M; case V4L2_PIX_FMT_RGB332: case V4L2_PIX_FMT_RGB444: case V4L2_PIX_FMT_RGB565: @@ -958,6 +958,7 @@ static int sh_veu_queue_init(void *priv, struct vb2_queue *src_vq, src_vq->ops = &sh_veu_qops; src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->lock = &veu->fop_lock; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; ret = vb2_queue_init(src_vq); if (ret < 0) @@ -971,6 +972,7 @@ static int sh_veu_queue_init(void *priv, struct vb2_queue *src_vq, dst_vq->ops = &sh_veu_qops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->lock = &veu->fop_lock; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; return vb2_queue_init(dst_vq); } @@ -1103,6 +1105,12 @@ static irqreturn_t sh_veu_isr(int irq, void *dev_id) if (!src || !dst) return IRQ_NONE; + dst->v4l2_buf.timestamp = src->v4l2_buf.timestamp; + dst->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst->v4l2_buf.flags |= + src->v4l2_buf.flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst->v4l2_buf.timecode = src->v4l2_buf.timecode; + spin_lock(&veu->lock); v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE); v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); diff --git a/drivers/media/platform/sh_vou.c b/drivers/media/platform/sh_vou.c index 8b799bae0..fe5c8ab06 100644 --- a/drivers/media/platform/sh_vou.c +++ b/drivers/media/platform/sh_vou.c @@ -27,7 +27,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-mediabus.h> -#include <media/videobuf-dma-contig.h> +#include <media/videobuf2-dma-contig.h> /* Mirror addresses are not available for all registers */ #define VOUER 0 @@ -57,31 +57,40 @@ enum sh_vou_status { SH_VOU_RUNNING, }; +#define VOU_MIN_IMAGE_WIDTH 16 #define VOU_MAX_IMAGE_WIDTH 720 -#define VOU_MAX_IMAGE_HEIGHT 576 +#define VOU_MIN_IMAGE_HEIGHT 16 + +struct sh_vou_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +static inline struct sh_vou_buffer *to_sh_vou_buffer(struct vb2_buffer *vb2) +{ + return container_of(vb2, struct sh_vou_buffer, vb); +} struct sh_vou_device { struct v4l2_device v4l2_dev; struct video_device vdev; - atomic_t use_count; struct sh_vou_pdata *pdata; spinlock_t lock; void __iomem *base; /* State information */ struct v4l2_pix_format pix; struct v4l2_rect rect; - struct list_head queue; + struct list_head buf_list; v4l2_std_id std; int pix_idx; - struct videobuf_buffer *active; + struct vb2_queue queue; + struct vb2_alloc_ctx *alloc_ctx; + struct sh_vou_buffer *active; enum sh_vou_status status; + unsigned sequence; struct mutex fop_lock; }; -struct sh_vou_file { - struct videobuf_queue vbq; -}; - /* Register access routines for sides A, B and mirror addresses */ static void sh_vou_reg_a_write(struct sh_vou_device *vou_dev, unsigned int reg, u32 value) @@ -133,6 +142,7 @@ struct sh_vou_fmt { u32 pfmt; char *desc; unsigned char bpp; + unsigned char bpl; unsigned char rgb; unsigned char yf; unsigned char pkf; @@ -143,6 +153,7 @@ static struct sh_vou_fmt vou_fmt[] = { { .pfmt = V4L2_PIX_FMT_NV12, .bpp = 12, + .bpl = 1, .desc = "YVU420 planar", .yf = 0, .rgb = 0, @@ -150,6 +161,7 @@ static struct sh_vou_fmt vou_fmt[] = { { .pfmt = V4L2_PIX_FMT_NV16, .bpp = 16, + .bpl = 1, .desc = "YVYU planar", .yf = 1, .rgb = 0, @@ -157,6 +169,7 @@ static struct sh_vou_fmt vou_fmt[] = { { .pfmt = V4L2_PIX_FMT_RGB24, .bpp = 24, + .bpl = 3, .desc = "RGB24", .pkf = 2, .rgb = 1, @@ -164,6 +177,7 @@ static struct sh_vou_fmt vou_fmt[] = { { .pfmt = V4L2_PIX_FMT_RGB565, .bpp = 16, + .bpl = 2, .desc = "RGB565", .pkf = 3, .rgb = 1, @@ -171,6 +185,7 @@ static struct sh_vou_fmt vou_fmt[] = { { .pfmt = V4L2_PIX_FMT_RGB565X, .bpp = 16, + .bpl = 2, .desc = "RGB565 byteswapped", .pkf = 3, .rgb = 1, @@ -178,11 +193,11 @@ static struct sh_vou_fmt vou_fmt[] = { }; static void sh_vou_schedule_next(struct sh_vou_device *vou_dev, - struct videobuf_buffer *vb) + struct vb2_buffer *vb) { dma_addr_t addr1, addr2; - addr1 = videobuf_to_dma_contig(vb); + addr1 = vb2_dma_contig_plane_dma_addr(vb, 0); switch (vou_dev->pix.pixelformat) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV16: @@ -196,8 +211,7 @@ static void sh_vou_schedule_next(struct sh_vou_device *vou_dev, sh_vou_reg_m_write(vou_dev, VOUAD2R, addr2); } -static void sh_vou_stream_start(struct sh_vou_device *vou_dev, - struct videobuf_buffer *vb) +static void sh_vou_stream_config(struct sh_vou_device *vou_dev) { unsigned int row_coeff; #ifdef __LITTLE_ENDIAN @@ -224,167 +238,136 @@ static void sh_vou_stream_start(struct sh_vou_device *vou_dev, sh_vou_reg_a_write(vou_dev, VOUSWR, dataswap); sh_vou_reg_ab_write(vou_dev, VOUAIR, vou_dev->pix.width * row_coeff); - sh_vou_schedule_next(vou_dev, vb); -} - -static void free_buffer(struct videobuf_queue *vq, struct videobuf_buffer *vb) -{ - BUG_ON(in_interrupt()); - - /* Wait until this buffer is no longer in STATE_QUEUED or STATE_ACTIVE */ - videobuf_waiton(vq, vb, 0, 0); - videobuf_dma_contig_free(vq, vb); - vb->state = VIDEOBUF_NEEDS_INIT; } /* Locking: caller holds fop_lock mutex */ -static int sh_vou_buf_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) +static int sh_vou_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) { - struct video_device *vdev = vq->priv_data; - struct sh_vou_device *vou_dev = video_get_drvdata(vdev); - - *size = vou_fmt[vou_dev->pix_idx].bpp * vou_dev->pix.width * - vou_dev->pix.height / 8; - - if (*count < 2) - *count = 2; - - /* Taking into account maximum frame size, *count will stay >= 2 */ - if (PAGE_ALIGN(*size) * *count > 4 * 1024 * 1024) - *count = 4 * 1024 * 1024 / PAGE_ALIGN(*size); + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix = &vou_dev->pix; + int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; - dev_dbg(vou_dev->v4l2_dev.dev, "%s(): count=%d, size=%d\n", __func__, - *count, *size); + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + if (fmt && fmt->fmt.pix.sizeimage < pix->height * bytes_per_line) + return -EINVAL; + *nplanes = 1; + sizes[0] = fmt ? fmt->fmt.pix.sizeimage : pix->height * bytes_per_line; + alloc_ctxs[0] = vou_dev->alloc_ctx; return 0; } -/* Locking: caller holds fop_lock mutex */ -static int sh_vou_buf_prepare(struct videobuf_queue *vq, - struct videobuf_buffer *vb, - enum v4l2_field field) +static int sh_vou_buf_prepare(struct vb2_buffer *vb) { - struct video_device *vdev = vq->priv_data; - struct sh_vou_device *vou_dev = video_get_drvdata(vdev); + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); struct v4l2_pix_format *pix = &vou_dev->pix; - int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; - int ret; + unsigned bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; + unsigned size = pix->height * bytes_per_line; dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - if (vb->width != pix->width || - vb->height != pix->height || - vb->field != pix->field) { - vb->width = pix->width; - vb->height = pix->height; - vb->field = field; - if (vb->state != VIDEOBUF_NEEDS_INIT) - free_buffer(vq, vb); - } - - vb->size = vb->height * bytes_per_line; - if (vb->baddr && vb->bsize < vb->size) { + if (vb2_plane_size(vb, 0) < size) { /* User buffer too small */ - dev_warn(vq->dev, "User buffer too small: [%zu] @ %lx\n", - vb->bsize, vb->baddr); + dev_warn(vou_dev->v4l2_dev.dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb, 0), size); return -EINVAL; } - if (vb->state == VIDEOBUF_NEEDS_INIT) { - ret = videobuf_iolock(vq, vb, NULL); - if (ret < 0) { - dev_warn(vq->dev, "IOLOCK buf-type %d: %d\n", - vb->memory, ret); - return ret; - } - vb->state = VIDEOBUF_PREPARED; - } - - dev_dbg(vou_dev->v4l2_dev.dev, - "%s(): fmt #%d, %u bytes per line, phys %pad, type %d, state %d\n", - __func__, vou_dev->pix_idx, bytes_per_line, - ({ dma_addr_t addr = videobuf_to_dma_contig(vb); &addr; }), - vb->memory, vb->state); - + vb2_set_plane_payload(vb, 0, size); return 0; } /* Locking: caller holds fop_lock mutex and vq->irqlock spinlock */ -static void sh_vou_buf_queue(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void sh_vou_buf_queue(struct vb2_buffer *vb) { - struct video_device *vdev = vq->priv_data; - struct sh_vou_device *vou_dev = video_get_drvdata(vdev); + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); + struct sh_vou_buffer *shbuf = to_sh_vou_buffer(vb); + unsigned long flags; - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + spin_lock_irqsave(&vou_dev->lock, flags); + list_add_tail(&shbuf->list, &vou_dev->buf_list); + spin_unlock_irqrestore(&vou_dev->lock, flags); +} + +static int sh_vou_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); + struct sh_vou_buffer *buf, *node; + int ret; - vb->state = VIDEOBUF_QUEUED; - list_add_tail(&vb->queue, &vou_dev->queue); - - if (vou_dev->status == SH_VOU_RUNNING) { - return; - } else if (!vou_dev->active) { - vou_dev->active = vb; - /* Start from side A: we use mirror addresses, so, set B */ - sh_vou_reg_a_write(vou_dev, VOURPR, 1); - dev_dbg(vou_dev->v4l2_dev.dev, "%s: first buffer status 0x%x\n", - __func__, sh_vou_reg_a_read(vou_dev, VOUSTR)); - sh_vou_schedule_next(vou_dev, vb); - /* Only activate VOU after the second buffer */ - } else if (vou_dev->active->queue.next == &vb->queue) { - /* Second buffer - initialise register side B */ - sh_vou_reg_a_write(vou_dev, VOURPR, 0); - sh_vou_stream_start(vou_dev, vb); - - /* Register side switching with frame VSYNC */ - sh_vou_reg_a_write(vou_dev, VOURCR, 5); - dev_dbg(vou_dev->v4l2_dev.dev, "%s: second buffer status 0x%x\n", - __func__, sh_vou_reg_a_read(vou_dev, VOUSTR)); - - /* Enable End-of-Frame (VSYNC) interrupts */ - sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004); - /* Two buffers on the queue - activate the hardware */ - - vou_dev->status = SH_VOU_RUNNING; - sh_vou_reg_a_write(vou_dev, VOUER, 0x107); + vou_dev->sequence = 0; + ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, + video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); + list_del(&buf->list); + } + vou_dev->active = NULL; + return ret; } + + buf = list_entry(vou_dev->buf_list.next, struct sh_vou_buffer, list); + + vou_dev->active = buf; + + /* Start from side A: we use mirror addresses, so, set B */ + sh_vou_reg_a_write(vou_dev, VOURPR, 1); + dev_dbg(vou_dev->v4l2_dev.dev, "%s: first buffer status 0x%x\n", + __func__, sh_vou_reg_a_read(vou_dev, VOUSTR)); + sh_vou_schedule_next(vou_dev, &buf->vb); + + buf = list_entry(buf->list.next, struct sh_vou_buffer, list); + + /* Second buffer - initialise register side B */ + sh_vou_reg_a_write(vou_dev, VOURPR, 0); + sh_vou_schedule_next(vou_dev, &buf->vb); + + /* Register side switching with frame VSYNC */ + sh_vou_reg_a_write(vou_dev, VOURCR, 5); + + sh_vou_stream_config(vou_dev); + /* Enable End-of-Frame (VSYNC) interrupts */ + sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004); + + /* Two buffers on the queue - activate the hardware */ + vou_dev->status = SH_VOU_RUNNING; + sh_vou_reg_a_write(vou_dev, VOUER, 0x107); + return 0; } -static void sh_vou_buf_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void sh_vou_stop_streaming(struct vb2_queue *vq) { - struct video_device *vdev = vq->priv_data; - struct sh_vou_device *vou_dev = video_get_drvdata(vdev); + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); + struct sh_vou_buffer *buf, *node; unsigned long flags; - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - + v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, + video, s_stream, 0); + /* disable output */ + sh_vou_reg_a_set(vou_dev, VOUER, 0, 1); + /* ...but the current frame will complete */ + sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000); + msleep(50); spin_lock_irqsave(&vou_dev->lock, flags); - - if (vou_dev->active == vb) { - /* disable output */ - sh_vou_reg_a_set(vou_dev, VOUER, 0, 1); - /* ...but the current frame will complete */ - sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000); - vou_dev->active = NULL; - } - - if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED)) { - vb->state = VIDEOBUF_ERROR; - list_del(&vb->queue); + list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + list_del(&buf->list); } - + vou_dev->active = NULL; spin_unlock_irqrestore(&vou_dev->lock, flags); - - free_buffer(vq, vb); } -static struct videobuf_queue_ops sh_vou_video_qops = { - .buf_setup = sh_vou_buf_setup, - .buf_prepare = sh_vou_buf_prepare, - .buf_queue = sh_vou_buf_queue, - .buf_release = sh_vou_buf_release, +static struct vb2_ops sh_vou_qops = { + .queue_setup = sh_vou_queue_setup, + .buf_prepare = sh_vou_buf_prepare, + .buf_queue = sh_vou_buf_queue, + .start_streaming = sh_vou_start_streaming, + .stop_streaming = sh_vou_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, }; /* Video IOCTLs */ @@ -396,7 +379,10 @@ static int sh_vou_querycap(struct file *file, void *priv, dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); strlcpy(cap->card, "SuperH VOU", sizeof(cap->card)); - cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + strlcpy(cap->driver, "sh-vou", sizeof(cap->driver)); + strlcpy(cap->bus_info, "platform:sh-vou", sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -540,8 +526,10 @@ static void vou_adjust_input(struct sh_vou_geometry *geo, v4l2_std_id std) img_height_max = 576; /* Image width must be a multiple of 4 */ - v4l_bound_align_image(&geo->in_width, 0, VOU_MAX_IMAGE_WIDTH, 2, - &geo->in_height, 0, img_height_max, 1, 0); + v4l_bound_align_image(&geo->in_width, + VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, + &geo->in_height, + VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); /* Select scales to come as close as possible to the output image */ for (i = ARRAY_SIZE(vou_scale_h_num) - 1; i >= 0; i--) { @@ -671,34 +659,19 @@ static void vou_adjust_output(struct sh_vou_geometry *geo, v4l2_std_id std) vou_scale_v_num[idx_v], vou_scale_v_den[idx_v], best); } -static int sh_vou_s_fmt_vid_out(struct file *file, void *priv, - struct v4l2_format *fmt) +static int sh_vou_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) { struct sh_vou_device *vou_dev = video_drvdata(file); struct v4l2_pix_format *pix = &fmt->fmt.pix; unsigned int img_height_max; int pix_idx; - struct sh_vou_geometry geo; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - /* Revisit: is this the correct code? */ - .format.code = MEDIA_BUS_FMT_YUYV8_2X8, - .format.field = V4L2_FIELD_INTERLACED, - .format.colorspace = V4L2_COLORSPACE_SMPTE170M, - }; - struct v4l2_mbus_framefmt *mbfmt = &format.format; - int ret; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__, - vou_dev->rect.width, vou_dev->rect.height, - pix->width, pix->height); - if (pix->field == V4L2_FIELD_ANY) - pix->field = V4L2_FIELD_NONE; + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - if (fmt->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || - pix->field != V4L2_FIELD_NONE) - return -EINVAL; + pix->field = V4L2_FIELD_INTERLACED; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->ycbcr_enc = pix->quantization = 0; for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) if (vou_fmt[pix_idx].pfmt == pix->pixelformat) @@ -712,9 +685,38 @@ static int sh_vou_s_fmt_vid_out(struct file *file, void *priv, else img_height_max = 576; - /* Image width must be a multiple of 4 */ - v4l_bound_align_image(&pix->width, 0, VOU_MAX_IMAGE_WIDTH, 2, - &pix->height, 0, img_height_max, 1, 0); + v4l_bound_align_image(&pix->width, + VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, + &pix->height, + VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); + pix->bytesperline = pix->width * vou_fmt[pix_idx].bpl; + pix->sizeimage = pix->height * ((pix->width * vou_fmt[pix_idx].bpp) >> 3); + + return 0; +} + +static int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev, + struct v4l2_pix_format *pix) +{ + unsigned int img_height_max; + struct sh_vou_geometry geo; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + /* Revisit: is this the correct code? */ + .format.code = MEDIA_BUS_FMT_YUYV8_2X8, + .format.field = V4L2_FIELD_INTERLACED, + .format.colorspace = V4L2_COLORSPACE_SMPTE170M, + }; + struct v4l2_mbus_framefmt *mbfmt = &format.format; + int pix_idx; + int ret; + + if (vb2_is_busy(&vou_dev->queue)) + return -EBUSY; + + for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) + if (vou_fmt[pix_idx].pfmt == pix->pixelformat) + break; geo.in_width = pix->width; geo.in_height = pix->height; @@ -733,6 +735,11 @@ static int sh_vou_s_fmt_vid_out(struct file *file, void *priv, dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__, geo.output.width, geo.output.height, mbfmt->width, mbfmt->height); + if (vou_dev->std & V4L2_STD_525_60) + img_height_max = 480; + else + img_height_max = 576; + /* Sanity checks */ if ((unsigned)mbfmt->width > VOU_MAX_IMAGE_WIDTH || (unsigned)mbfmt->height > img_height_max || @@ -765,109 +772,39 @@ static int sh_vou_s_fmt_vid_out(struct file *file, void *priv, return 0; } -static int sh_vou_try_fmt_vid_out(struct file *file, void *priv, - struct v4l2_format *fmt) +static int sh_vou_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) { struct sh_vou_device *vou_dev = video_drvdata(file); - struct v4l2_pix_format *pix = &fmt->fmt.pix; - int i; + int ret = sh_vou_try_fmt_vid_out(file, priv, fmt); - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - pix->field = V4L2_FIELD_NONE; - - v4l_bound_align_image(&pix->width, 0, VOU_MAX_IMAGE_WIDTH, 1, - &pix->height, 0, VOU_MAX_IMAGE_HEIGHT, 1, 0); - - for (i = 0; i < ARRAY_SIZE(vou_fmt); i++) - if (vou_fmt[i].pfmt == pix->pixelformat) - return 0; - - pix->pixelformat = vou_fmt[0].pfmt; - - return 0; + if (ret) + return ret; + return sh_vou_set_fmt_vid_out(vou_dev, &fmt->fmt.pix); } -static int sh_vou_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *req) +static int sh_vou_enum_output(struct file *file, void *fh, + struct v4l2_output *a) { struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = priv; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - if (req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + if (a->index) return -EINVAL; - - return videobuf_reqbufs(&vou_file->vbq, req); -} - -static int sh_vou_querybuf(struct file *file, void *priv, - struct v4l2_buffer *b) -{ - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = priv; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - return videobuf_querybuf(&vou_file->vbq, b); -} - -static int sh_vou_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) -{ - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = priv; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - return videobuf_qbuf(&vou_file->vbq, b); + strlcpy(a->name, "Video Out", sizeof(a->name)); + a->type = V4L2_OUTPUT_TYPE_ANALOG; + a->std = vou_dev->vdev.tvnorms; + return 0; } -static int sh_vou_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +static int sh_vou_g_output(struct file *file, void *fh, unsigned int *i) { - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = priv; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - return videobuf_dqbuf(&vou_file->vbq, b, file->f_flags & O_NONBLOCK); + *i = 0; + return 0; } -static int sh_vou_streamon(struct file *file, void *priv, - enum v4l2_buf_type buftype) +static int sh_vou_s_output(struct file *file, void *fh, unsigned int i) { - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = priv; - int ret; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, - video, s_stream, 1); - if (ret < 0 && ret != -ENOIOCTLCMD) - return ret; - - /* This calls our .buf_queue() (== sh_vou_buf_queue) */ - return videobuf_streamon(&vou_file->vbq); -} - -static int sh_vou_streamoff(struct file *file, void *priv, - enum v4l2_buf_type buftype) -{ - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = priv; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - /* - * This calls buf_release from host driver's videobuf_queue_ops for all - * remaining buffers. When the last buffer is freed, stop streaming - */ - videobuf_streamoff(&vou_file->vbq); - v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video, s_stream, 0); - - return 0; + return i ? -EINVAL : 0; } static u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt) @@ -892,8 +829,11 @@ static int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id std_id) dev_dbg(vou_dev->v4l2_dev.dev, "%s(): 0x%llx\n", __func__, std_id); - if (std_id & ~vou_dev->vdev.tvnorms) - return -EINVAL; + if (std_id == vou_dev->std) + return 0; + + if (vb2_is_busy(&vou_dev->queue)) + return -EBUSY; ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video, s_std_output, std_id); @@ -901,13 +841,25 @@ static int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id std_id) if (ret < 0 && ret != -ENOIOCTLCMD) return ret; - if (std_id & V4L2_STD_525_60) + vou_dev->rect.top = vou_dev->rect.left = 0; + vou_dev->rect.width = VOU_MAX_IMAGE_WIDTH; + if (std_id & V4L2_STD_525_60) { sh_vou_reg_ab_set(vou_dev, VOUCR, sh_vou_ntsc_mode(vou_dev->pdata->bus_fmt) << 29, 7 << 29); - else + vou_dev->rect.height = 480; + } else { sh_vou_reg_ab_set(vou_dev, VOUCR, 5 << 29, 7 << 29); + vou_dev->rect.height = 576; + } + vou_dev->pix.width = vou_dev->rect.width; + vou_dev->pix.height = vou_dev->rect.height; + vou_dev->pix.bytesperline = + vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpl; + vou_dev->pix.sizeimage = vou_dev->pix.height * + ((vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpp) >> 3); vou_dev->std = std_id; + sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); return 0; } @@ -923,24 +875,66 @@ static int sh_vou_g_std(struct file *file, void *priv, v4l2_std_id *std) return 0; } -static int sh_vou_g_crop(struct file *file, void *fh, struct v4l2_crop *a) +static int sh_vou_log_status(struct file *file, void *priv) { struct sh_vou_device *vou_dev = video_drvdata(file); - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + pr_info("VOUER: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUER)); + pr_info("VOUCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUCR)); + pr_info("VOUSTR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSTR)); + pr_info("VOUVCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVCR)); + pr_info("VOUISR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUISR)); + pr_info("VOUBCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUBCR)); + pr_info("VOUDPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDPR)); + pr_info("VOUDSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDSR)); + pr_info("VOUVPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVPR)); + pr_info("VOUIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUIR)); + pr_info("VOUSRR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSRR)); + pr_info("VOUMSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUMSR)); + pr_info("VOUHIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUHIR)); + pr_info("VOUDFR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDFR)); + pr_info("VOUAD1R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD1R)); + pr_info("VOUAD2R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD2R)); + pr_info("VOUAIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAIR)); + pr_info("VOUSWR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSWR)); + pr_info("VOURCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURCR)); + pr_info("VOURPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURPR)); + return 0; +} - a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - a->c = vou_dev->rect; +static int sh_vou_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE: + sel->r = vou_dev->rect; + break; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = VOU_MAX_IMAGE_WIDTH; + if (vou_dev->std & V4L2_STD_525_60) + sel->r.height = 480; + else + sel->r.height = 576; + break; + default: + return -EINVAL; + } return 0; } /* Assume a dull encoder, do all the work ourselves. */ -static int sh_vou_s_crop(struct file *file, void *fh, const struct v4l2_crop *a) +static int sh_vou_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) { - struct v4l2_crop a_writable = *a; + struct v4l2_rect *rect = &sel->r; struct sh_vou_device *vou_dev = video_drvdata(file); - struct v4l2_rect *rect = &a_writable.c; struct v4l2_crop sd_crop = {.type = V4L2_BUF_TYPE_VIDEO_OUTPUT}; struct v4l2_pix_format *pix = &vou_dev->pix; struct sh_vou_geometry geo; @@ -954,19 +948,22 @@ static int sh_vou_s_crop(struct file *file, void *fh, const struct v4l2_crop *a) unsigned int img_height_max; int ret; - dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u@%u:%u\n", __func__, - rect->width, rect->height, rect->left, rect->top); - - if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + sel->target != V4L2_SEL_TGT_COMPOSE) return -EINVAL; + if (vb2_is_busy(&vou_dev->queue)) + return -EBUSY; + if (vou_dev->std & V4L2_STD_525_60) img_height_max = 480; else img_height_max = 576; - v4l_bound_align_image(&rect->width, 0, VOU_MAX_IMAGE_WIDTH, 1, - &rect->height, 0, img_height_max, 1, 0); + v4l_bound_align_image(&rect->width, + VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 1, + &rect->height, + VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); if (rect->width + rect->left > VOU_MAX_IMAGE_WIDTH) rect->left = VOU_MAX_IMAGE_WIDTH - rect->width; @@ -1021,41 +1018,11 @@ static int sh_vou_s_crop(struct file *file, void *fh, const struct v4l2_crop *a) return 0; } -/* - * Total field: NTSC 858 x 2 * 262/263, PAL 864 x 2 * 312/313, default rectangle - * is the initial register values, height takes the interlaced format into - * account. The actual image can only go up to 720 x 2 * 240, So, VOUVPR can - * actually only meaningfully contain values <= 720 and <= 240 respectively, and - * not <= 864 and <= 312. - */ -static int sh_vou_cropcap(struct file *file, void *priv, - struct v4l2_cropcap *a) -{ - struct sh_vou_device *vou_dev = video_drvdata(file); - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - a->bounds.left = 0; - a->bounds.top = 0; - a->bounds.width = VOU_MAX_IMAGE_WIDTH; - a->bounds.height = VOU_MAX_IMAGE_HEIGHT; - /* Default = max, set VOUDPR = 0, which is not hardware default */ - a->defrect.left = 0; - a->defrect.top = 0; - a->defrect.width = VOU_MAX_IMAGE_WIDTH; - a->defrect.height = VOU_MAX_IMAGE_HEIGHT; - a->pixelaspect.numerator = 1; - a->pixelaspect.denominator = 1; - - return 0; -} - static irqreturn_t sh_vou_isr(int irq, void *dev_id) { struct sh_vou_device *vou_dev = dev_id; static unsigned long j; - struct videobuf_buffer *vb; + struct sh_vou_buffer *vb; static int cnt; u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked; u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR); @@ -1068,7 +1035,7 @@ static irqreturn_t sh_vou_isr(int irq, void *dev_id) } spin_lock(&vou_dev->lock); - if (!vou_dev->active || list_empty(&vou_dev->queue)) { + if (!vou_dev->active || list_empty(&vou_dev->buf_list)) { if (printk_timed_ratelimit(&j, 500)) dev_warn(vou_dev->v4l2_dev.dev, "IRQ without active buffer: %x!\n", irq_status); @@ -1090,33 +1057,30 @@ static irqreturn_t sh_vou_isr(int irq, void *dev_id) sh_vou_reg_a_write(vou_dev, VOUIR, masked); vb = vou_dev->active; - list_del(&vb->queue); - - vb->state = VIDEOBUF_DONE; - v4l2_get_timestamp(&vb->ts); - vb->field_count++; - wake_up(&vb->done); - - if (list_empty(&vou_dev->queue)) { - /* Stop VOU */ - dev_dbg(vou_dev->v4l2_dev.dev, "%s: queue empty after %d\n", - __func__, cnt); - sh_vou_reg_a_set(vou_dev, VOUER, 0, 1); - vou_dev->active = NULL; - vou_dev->status = SH_VOU_INITIALISING; - /* Disable End-of-Frame (VSYNC) interrupts */ - sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000); + if (list_is_singular(&vb->list)) { + /* Keep cycling while no next buffer is available */ + sh_vou_schedule_next(vou_dev, &vb->vb); spin_unlock(&vou_dev->lock); return IRQ_HANDLED; } - vou_dev->active = list_entry(vou_dev->queue.next, - struct videobuf_buffer, queue); + list_del(&vb->list); + + v4l2_get_timestamp(&vb->vb.v4l2_buf.timestamp); + vb->vb.v4l2_buf.sequence = vou_dev->sequence++; + vb->vb.v4l2_buf.field = V4L2_FIELD_INTERLACED; + vb2_buffer_done(&vb->vb, VB2_BUF_STATE_DONE); - if (vou_dev->active->queue.next != &vou_dev->queue) { - struct videobuf_buffer *new = list_entry(vou_dev->active->queue.next, - struct videobuf_buffer, queue); - sh_vou_schedule_next(vou_dev, new); + vou_dev->active = list_entry(vou_dev->buf_list.next, + struct sh_vou_buffer, list); + + if (list_is_singular(&vou_dev->buf_list)) { + /* Keep cycling while no next buffer is available */ + sh_vou_schedule_next(vou_dev, &vou_dev->active->vb); + } else { + struct sh_vou_buffer *new = list_entry(vou_dev->active->list.next, + struct sh_vou_buffer, list); + sh_vou_schedule_next(vou_dev, &new->vb); } spin_unlock(&vou_dev->lock); @@ -1156,6 +1120,8 @@ static int sh_vou_hw_init(struct sh_vou_device *vou_dev) /* Default - fixed HSYNC length, can be made configurable is required */ sh_vou_reg_ab_write(vou_dev, VOUMSR, 0x800000); + sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); + return 0; } @@ -1163,96 +1129,47 @@ static int sh_vou_hw_init(struct sh_vou_device *vou_dev) static int sh_vou_open(struct file *file) { struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = kzalloc(sizeof(struct sh_vou_file), - GFP_KERNEL); - - if (!vou_file) - return -ENOMEM; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + int err; - if (mutex_lock_interruptible(&vou_dev->fop_lock)) { - kfree(vou_file); + if (mutex_lock_interruptible(&vou_dev->fop_lock)) return -ERESTARTSYS; - } - if (atomic_inc_return(&vou_dev->use_count) == 1) { - int ret; + + err = v4l2_fh_open(file); + if (err) + goto done_open; + if (v4l2_fh_is_singular_file(file) && + vou_dev->status == SH_VOU_INITIALISING) { /* First open */ - vou_dev->status = SH_VOU_INITIALISING; pm_runtime_get_sync(vou_dev->v4l2_dev.dev); - ret = sh_vou_hw_init(vou_dev); - if (ret < 0) { - atomic_dec(&vou_dev->use_count); + err = sh_vou_hw_init(vou_dev); + if (err < 0) { pm_runtime_put(vou_dev->v4l2_dev.dev); + v4l2_fh_release(file); + } else { vou_dev->status = SH_VOU_IDLE; - mutex_unlock(&vou_dev->fop_lock); - kfree(vou_file); - return ret; } } - - videobuf_queue_dma_contig_init(&vou_file->vbq, &sh_vou_video_qops, - vou_dev->v4l2_dev.dev, &vou_dev->lock, - V4L2_BUF_TYPE_VIDEO_OUTPUT, - V4L2_FIELD_NONE, - sizeof(struct videobuf_buffer), - &vou_dev->vdev, &vou_dev->fop_lock); +done_open: mutex_unlock(&vou_dev->fop_lock); - - file->private_data = vou_file; - - return 0; + return err; } static int sh_vou_release(struct file *file) { struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = file->private_data; + bool is_last; - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - if (!atomic_dec_return(&vou_dev->use_count)) { - mutex_lock(&vou_dev->fop_lock); + mutex_lock(&vou_dev->fop_lock); + is_last = v4l2_fh_is_singular_file(file); + _vb2_fop_release(file, NULL); + if (is_last) { /* Last close */ - vou_dev->status = SH_VOU_IDLE; + vou_dev->status = SH_VOU_INITIALISING; sh_vou_reg_a_set(vou_dev, VOUER, 0, 0x101); pm_runtime_put(vou_dev->v4l2_dev.dev); - mutex_unlock(&vou_dev->fop_lock); } - - file->private_data = NULL; - kfree(vou_file); - - return 0; -} - -static int sh_vou_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = file->private_data; - int ret; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - if (mutex_lock_interruptible(&vou_dev->fop_lock)) - return -ERESTARTSYS; - ret = videobuf_mmap_mapper(&vou_file->vbq, vma); mutex_unlock(&vou_dev->fop_lock); - return ret; -} - -static unsigned int sh_vou_poll(struct file *file, poll_table *wait) -{ - struct sh_vou_device *vou_dev = video_drvdata(file); - struct sh_vou_file *vou_file = file->private_data; - unsigned int res; - - dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); - - mutex_lock(&vou_dev->fop_lock); - res = videobuf_poll_stream(file, &vou_file->vbq, wait); - mutex_unlock(&vou_dev->fop_lock); - return res; + return 0; } /* sh_vou display ioctl operations */ @@ -1262,17 +1179,23 @@ static const struct v4l2_ioctl_ops sh_vou_ioctl_ops = { .vidioc_g_fmt_vid_out = sh_vou_g_fmt_vid_out, .vidioc_s_fmt_vid_out = sh_vou_s_fmt_vid_out, .vidioc_try_fmt_vid_out = sh_vou_try_fmt_vid_out, - .vidioc_reqbufs = sh_vou_reqbufs, - .vidioc_querybuf = sh_vou_querybuf, - .vidioc_qbuf = sh_vou_qbuf, - .vidioc_dqbuf = sh_vou_dqbuf, - .vidioc_streamon = sh_vou_streamon, - .vidioc_streamoff = sh_vou_streamoff, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_g_output = sh_vou_g_output, + .vidioc_s_output = sh_vou_s_output, + .vidioc_enum_output = sh_vou_enum_output, .vidioc_s_std = sh_vou_s_std, .vidioc_g_std = sh_vou_g_std, - .vidioc_cropcap = sh_vou_cropcap, - .vidioc_g_crop = sh_vou_g_crop, - .vidioc_s_crop = sh_vou_s_crop, + .vidioc_g_selection = sh_vou_g_selection, + .vidioc_s_selection = sh_vou_s_selection, + .vidioc_log_status = sh_vou_log_status, }; static const struct v4l2_file_operations sh_vou_fops = { @@ -1280,8 +1203,9 @@ static const struct v4l2_file_operations sh_vou_fops = { .open = sh_vou_open, .release = sh_vou_release, .unlocked_ioctl = video_ioctl2, - .mmap = sh_vou_mmap, - .poll = sh_vou_poll, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, + .write = vb2_fop_write, }; static const struct video_device sh_vou_video_template = { @@ -1300,8 +1224,9 @@ static int sh_vou_probe(struct platform_device *pdev) struct i2c_adapter *i2c_adap; struct video_device *vdev; struct sh_vou_device *vou_dev; - struct resource *reg_res, *region; + struct resource *reg_res; struct v4l2_subdev *subdev; + struct vb2_queue *q; int irq, ret; reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1312,16 +1237,16 @@ static int sh_vou_probe(struct platform_device *pdev) return -ENODEV; } - vou_dev = kzalloc(sizeof(*vou_dev), GFP_KERNEL); + vou_dev = devm_kzalloc(&pdev->dev, sizeof(*vou_dev), GFP_KERNEL); if (!vou_dev) return -ENOMEM; - INIT_LIST_HEAD(&vou_dev->queue); + INIT_LIST_HEAD(&vou_dev->buf_list); spin_lock_init(&vou_dev->lock); mutex_init(&vou_dev->fop_lock); - atomic_set(&vou_dev->use_count, 0); vou_dev->pdata = vou_pdata; - vou_dev->status = SH_VOU_IDLE; + vou_dev->status = SH_VOU_INITIALISING; + vou_dev->pix_idx = 1; rect = &vou_dev->rect; pix = &vou_dev->pix; @@ -1334,34 +1259,24 @@ static int sh_vou_probe(struct platform_device *pdev) rect->height = 480; pix->width = VOU_MAX_IMAGE_WIDTH; pix->height = 480; - pix->pixelformat = V4L2_PIX_FMT_YVYU; - pix->field = V4L2_FIELD_NONE; - pix->bytesperline = VOU_MAX_IMAGE_WIDTH * 2; + pix->pixelformat = V4L2_PIX_FMT_NV16; + pix->field = V4L2_FIELD_INTERLACED; + pix->bytesperline = VOU_MAX_IMAGE_WIDTH; pix->sizeimage = VOU_MAX_IMAGE_WIDTH * 2 * 480; pix->colorspace = V4L2_COLORSPACE_SMPTE170M; - region = request_mem_region(reg_res->start, resource_size(reg_res), - pdev->name); - if (!region) { - dev_err(&pdev->dev, "VOU region already claimed\n"); - ret = -EBUSY; - goto ereqmemreg; - } + vou_dev->base = devm_ioremap_resource(&pdev->dev, reg_res); + if (IS_ERR(vou_dev->base)) + return PTR_ERR(vou_dev->base); - vou_dev->base = ioremap(reg_res->start, resource_size(reg_res)); - if (!vou_dev->base) { - ret = -ENOMEM; - goto emap; - } - - ret = request_irq(irq, sh_vou_isr, 0, "vou", vou_dev); + ret = devm_request_irq(&pdev->dev, irq, sh_vou_isr, 0, "vou", vou_dev); if (ret < 0) - goto ereqirq; + return ret; ret = v4l2_device_register(&pdev->dev, &vou_dev->v4l2_dev); if (ret < 0) { dev_err(&pdev->dev, "Error registering v4l2 device\n"); - goto ev4l2devreg; + return ret; } vdev = &vou_dev->vdev; @@ -1374,6 +1289,30 @@ static int sh_vou_probe(struct platform_device *pdev) video_set_drvdata(vdev, vou_dev); + /* Initialize the vb2 queue */ + q = &vou_dev->queue; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE; + q->drv_priv = vou_dev; + q->buf_struct_size = sizeof(struct sh_vou_buffer); + q->ops = &sh_vou_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->lock = &vou_dev->fop_lock; + ret = vb2_queue_init(q); + if (ret) + goto einitctx; + + vou_dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(vou_dev->alloc_ctx)) { + dev_err(&pdev->dev, "Can't allocate buffer context"); + ret = PTR_ERR(vou_dev->alloc_ctx); + goto einitctx; + } + vdev->queue = q; + INIT_LIST_HEAD(&vou_dev->buf_list); + pm_runtime_enable(&pdev->dev); pm_runtime_resume(&pdev->dev); @@ -1405,41 +1344,27 @@ ei2cnd: ereset: i2c_put_adapter(i2c_adap); ei2cgadap: + vb2_dma_contig_cleanup_ctx(vou_dev->alloc_ctx); +einitctx: pm_runtime_disable(&pdev->dev); v4l2_device_unregister(&vou_dev->v4l2_dev); -ev4l2devreg: - free_irq(irq, vou_dev); -ereqirq: - iounmap(vou_dev->base); -emap: - release_mem_region(reg_res->start, resource_size(reg_res)); -ereqmemreg: - kfree(vou_dev); return ret; } static int sh_vou_remove(struct platform_device *pdev) { - int irq = platform_get_irq(pdev, 0); struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); struct sh_vou_device *vou_dev = container_of(v4l2_dev, struct sh_vou_device, v4l2_dev); struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next, struct v4l2_subdev, list); struct i2c_client *client = v4l2_get_subdevdata(sd); - struct resource *reg_res; - if (irq > 0) - free_irq(irq, vou_dev); pm_runtime_disable(&pdev->dev); video_unregister_device(&vou_dev->vdev); i2c_put_adapter(client->adapter); + vb2_dma_contig_cleanup_ctx(vou_dev->alloc_ctx); v4l2_device_unregister(&vou_dev->v4l2_dev); - iounmap(vou_dev->base); - reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (reg_res) - release_mem_region(reg_res->start, resource_size(reg_res)); - kfree(vou_dev); return 0; } diff --git a/drivers/media/platform/soc_camera/atmel-isi.c b/drivers/media/platform/soc_camera/atmel-isi.c index 287902681..90701726a 100644 --- a/drivers/media/platform/soc_camera/atmel-isi.c +++ b/drivers/media/platform/soc_camera/atmel-isi.c @@ -20,6 +20,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/slab.h> #include <media/atmel-isi.h> @@ -34,7 +35,6 @@ #define VID_LIMIT_BYTES (16 * 1024 * 1024) #define MIN_FRAME_RATE 15 #define FRAME_INTERVAL_MILLI_SEC (1000 / MIN_FRAME_RATE) -#define ISI_DEFAULT_MCLK_FREQ 25000000 /* Frame buffer descriptor */ struct fbd { @@ -82,8 +82,6 @@ struct atmel_isi { struct completion complete; /* ISI peripherial clock */ struct clk *pclk; - /* ISI_MCK, feed to camera sensor to generate pixel clock */ - struct clk *mck; unsigned int irq; struct isi_platform_data pdata; @@ -386,10 +384,13 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) struct atmel_isi *isi = ici->priv; int ret; + pm_runtime_get_sync(ici->v4l2_dev.dev); + /* Reset ISI */ ret = atmel_isi_wait_status(isi, WAIT_ISI_RESET); if (ret < 0) { dev_err(icd->parent, "Reset ISI timed out\n"); + pm_runtime_put(ici->v4l2_dev.dev); return ret; } /* Disable all interrupts */ @@ -431,11 +432,9 @@ static void stop_streaming(struct vb2_queue *vq) time_before(jiffies, timeout)) msleep(1); - if (time_after(jiffies, timeout)) { + if (time_after(jiffies, timeout)) dev_err(icd->parent, "Timeout waiting for finishing codec request\n"); - return; - } /* Disable interrupts */ isi_writel(isi, ISI_INTDIS, @@ -445,6 +444,8 @@ static void stop_streaming(struct vb2_queue *vq) ret = atmel_isi_wait_status(isi, WAIT_ISI_DISABLE); if (ret < 0) dev_err(icd->parent, "Disable ISI timed out\n"); + + pm_runtime_put(ici->v4l2_dev.dev); } static struct vb2_ops isi_video_qops = { @@ -516,7 +517,13 @@ static int isi_camera_set_fmt(struct soc_camera_device *icd, if (mf->code != xlate->code) return -EINVAL; + /* Enable PM and peripheral clock before operate isi registers */ + pm_runtime_get_sync(ici->v4l2_dev.dev); + ret = configure_geometry(isi, pix->width, pix->height, xlate->code); + + pm_runtime_put(ici->v4l2_dev.dev); + if (ret < 0) return ret; @@ -730,37 +737,6 @@ static void isi_camera_remove_device(struct soc_camera_device *icd) icd->devnum); } -/* Called with .host_lock held */ -static int isi_camera_clock_start(struct soc_camera_host *ici) -{ - struct atmel_isi *isi = ici->priv; - int ret; - - ret = clk_prepare_enable(isi->pclk); - if (ret) - return ret; - - if (!IS_ERR(isi->mck)) { - ret = clk_prepare_enable(isi->mck); - if (ret) { - clk_disable_unprepare(isi->pclk); - return ret; - } - } - - return 0; -} - -/* Called with .host_lock held */ -static void isi_camera_clock_stop(struct soc_camera_host *ici) -{ - struct atmel_isi *isi = ici->priv; - - if (!IS_ERR(isi->mck)) - clk_disable_unprepare(isi->mck); - clk_disable_unprepare(isi->pclk); -} - static unsigned int isi_camera_poll(struct file *file, poll_table *pt) { struct soc_camera_device *icd = file->private_data; @@ -855,9 +831,14 @@ static int isi_camera_set_bus_param(struct soc_camera_device *icd) cfg1 |= ISI_CFG1_THMASK_BEATS_16; + /* Enable PM and peripheral clock before operate isi registers */ + pm_runtime_get_sync(ici->v4l2_dev.dev); + isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS); isi_writel(isi, ISI_CFG1, cfg1); + pm_runtime_put(ici->v4l2_dev.dev); + return 0; } @@ -865,8 +846,6 @@ static struct soc_camera_host_ops isi_soc_camera_host_ops = { .owner = THIS_MODULE, .add = isi_camera_add_device, .remove = isi_camera_remove_device, - .clock_start = isi_camera_clock_start, - .clock_stop = isi_camera_clock_stop, .set_fmt = isi_camera_set_fmt, .try_fmt = isi_camera_try_fmt, .get_formats = isi_camera_get_formats, @@ -889,6 +868,7 @@ static int atmel_isi_remove(struct platform_device *pdev) sizeof(struct fbd) * MAX_BUFFER_NUM, isi->p_fb_descriptors, isi->fb_descriptors_phys); + pm_runtime_disable(&pdev->dev); return 0; } @@ -902,7 +882,6 @@ static int atmel_isi_probe_dt(struct atmel_isi *isi, /* Default settings for ISI */ isi->pdata.full_mode = 1; - isi->pdata.mck_hz = ISI_DEFAULT_MCLK_FREQ; isi->pdata.frate = ISI_CFG1_FRATE_CAPTURE_ALL; np = of_graph_get_next_endpoint(np, NULL); @@ -978,21 +957,6 @@ static int atmel_isi_probe(struct platform_device *pdev) INIT_LIST_HEAD(&isi->video_buffer_list); INIT_LIST_HEAD(&isi->dma_desc_head); - /* ISI_MCK is the sensor master clock. It should be handled by the - * sensor driver directly, as the ISI has no use for that clock. Make - * the clock optional here while platforms transition to the correct - * model. - */ - isi->mck = devm_clk_get(dev, "isi_mck"); - if (!IS_ERR(isi->mck)) { - /* Set ISI_MCK's frequency, it should be faster than pixel - * clock. - */ - ret = clk_set_rate(isi->mck, isi->pdata.mck_hz); - if (ret < 0) - return ret; - } - isi->p_fb_descriptors = dma_alloc_coherent(&pdev->dev, sizeof(struct fbd) * MAX_BUFFER_NUM, &isi->fb_descriptors_phys, @@ -1027,8 +991,6 @@ static int atmel_isi_probe(struct platform_device *pdev) if (isi->pdata.data_width_flags & ISI_DATAWIDTH_10) isi->width_flags |= 1 << 9; - isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS); - irq = platform_get_irq(pdev, 0); if (IS_ERR_VALUE(irq)) { ret = irq; @@ -1049,6 +1011,9 @@ static int atmel_isi_probe(struct platform_device *pdev) soc_host->v4l2_dev.dev = &pdev->dev; soc_host->nr = pdev->id; + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + if (isi->pdata.asd_sizes) { soc_host->asd = isi->pdata.asd; soc_host->asd_sizes = isi->pdata.asd_sizes; @@ -1062,6 +1027,7 @@ static int atmel_isi_probe(struct platform_device *pdev) return 0; err_register_soc_camera_host: + pm_runtime_disable(&pdev->dev); err_req_irq: err_ioremap: vb2_dma_contig_cleanup_ctx(isi->alloc_ctx); @@ -1074,6 +1040,30 @@ err_alloc_ctx: return ret; } +static int atmel_isi_runtime_suspend(struct device *dev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(dev); + struct atmel_isi *isi = container_of(soc_host, + struct atmel_isi, soc_host); + + clk_disable_unprepare(isi->pclk); + + return 0; +} +static int atmel_isi_runtime_resume(struct device *dev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(dev); + struct atmel_isi *isi = container_of(soc_host, + struct atmel_isi, soc_host); + + return clk_prepare_enable(isi->pclk); +} + +static const struct dev_pm_ops atmel_isi_dev_pm_ops = { + SET_RUNTIME_PM_OPS(atmel_isi_runtime_suspend, + atmel_isi_runtime_resume, NULL) +}; + static const struct of_device_id atmel_isi_of_match[] = { { .compatible = "atmel,at91sam9g45-isi" }, { } @@ -1085,6 +1075,7 @@ static struct platform_driver atmel_isi_driver = { .driver = { .name = "atmel_isi", .of_match_table = of_match_ptr(atmel_isi_of_match), + .pm = &atmel_isi_dev_pm_ops, }, }; diff --git a/drivers/media/platform/soc_camera/rcar_vin.c b/drivers/media/platform/soc_camera/rcar_vin.c index db7700b0a..71dd71c0b 100644 --- a/drivers/media/platform/soc_camera/rcar_vin.c +++ b/drivers/media/platform/soc_camera/rcar_vin.c @@ -98,6 +98,7 @@ #define VNMC_INF_YUV10_BT656 (2 << 16) #define VNMC_INF_YUV10_BT601 (3 << 16) #define VNMC_INF_YUV16 (5 << 16) +#define VNMC_INF_RGB888 (6 << 16) #define VNMC_VUP (1 << 10) #define VNMC_IM_ODD (0 << 3) #define VNMC_IM_ODD_EVEN (1 << 3) @@ -540,6 +541,9 @@ static int rcar_vin_videobuf_setup(struct vb2_queue *vq, unsigned int bytes_per_line; int ret; + if (fmt->fmt.pix.sizeimage < icd->sizeimage) + return -EINVAL; + xlate = soc_camera_xlate_by_fourcc(icd, fmt->fmt.pix.pixelformat); if (!xlate) @@ -589,7 +593,7 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) struct soc_camera_device *icd = priv->ici.icd; struct rcar_vin_cam *cam = icd->host_priv; u32 vnmc, dmr, interrupts; - bool progressive = false, output_is_yuv = false; + bool progressive = false, output_is_yuv = false, input_is_yuv = false; switch (priv->field) { case V4L2_FIELD_TOP: @@ -623,16 +627,22 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) case MEDIA_BUS_FMT_YUYV8_1X16: /* BT.601/BT.1358 16bit YCbCr422 */ vnmc |= VNMC_INF_YUV16; + input_is_yuv = true; break; case MEDIA_BUS_FMT_YUYV8_2X8: /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ vnmc |= priv->pdata_flags & RCAR_VIN_BT656 ? VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + vnmc |= VNMC_INF_RGB888; break; case MEDIA_BUS_FMT_YUYV10_2X10: /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */ vnmc |= priv->pdata_flags & RCAR_VIN_BT656 ? VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601; + input_is_yuv = true; break; default: break; @@ -676,7 +686,7 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) vnmc |= VNMC_VUP; /* If input and output use the same colorspace, use bypass mode */ - if (output_is_yuv) + if (input_is_yuv == output_is_yuv) vnmc |= VNMC_BPS; /* progressive or interlaced mode */ @@ -1423,6 +1433,7 @@ static int rcar_vin_get_formats(struct soc_camera_device *icd, unsigned int idx, case MEDIA_BUS_FMT_YUYV8_1X16: case MEDIA_BUS_FMT_YUYV8_2X8: case MEDIA_BUS_FMT_YUYV10_2X10: + case MEDIA_BUS_FMT_RGB888_1X24: if (cam->extra_fmt) break; @@ -1783,6 +1794,7 @@ static int rcar_vin_querycap(struct soc_camera_host *ici, strlcpy(cap->card, "R_Car_VIN", sizeof(cap->card)); cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s%d", DRV_NAME, ici->nr); return 0; } diff --git a/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c b/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c index c5c6c4e91..efdeea449 100644 --- a/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c +++ b/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c @@ -1665,6 +1665,8 @@ static int sh_mobile_ceu_querycap(struct soc_camera_host *ici, struct v4l2_capability *cap) { strlcpy(cap->card, "SuperH_Mobile_CEU", sizeof(cap->card)); + strlcpy(cap->driver, "sh_mobile_ceu", sizeof(cap->driver)); + strlcpy(cap->bus_info, "platform:sh_mobile_ceu", sizeof(cap->bus_info)); cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; @@ -1773,6 +1775,7 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev) pcdev->max_height = pcdev->pdata->max_height; pcdev->flags = pcdev->pdata->flags; } + pcdev->field = V4L2_FIELD_NONE; if (!pcdev->max_width) { unsigned int v; diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index d708df410..9087fed58 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -309,11 +309,14 @@ static int soc_camera_try_fmt_vid_cap(struct file *file, void *priv, static int soc_camera_enum_input(struct file *file, void *priv, struct v4l2_input *inp) { + struct soc_camera_device *icd = file->private_data; + if (inp->index != 0) return -EINVAL; /* default is camera */ inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = icd->vdev->tvnorms; strcpy(inp->name, "Camera"); return 0; @@ -381,9 +384,8 @@ static int soc_camera_reqbufs(struct file *file, void *priv, ret = vb2_reqbufs(&icd->vb2_vidq, p); } - if (!ret && !icd->streamer) - icd->streamer = file; - + if (!ret) + icd->streamer = p->count ? file : NULL; return ret; } @@ -440,12 +442,19 @@ static int soc_camera_create_bufs(struct file *file, void *priv, { struct soc_camera_device *icd = file->private_data; struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + int ret; /* videobuf2 only */ if (ici->ops->init_videobuf) - return -EINVAL; - else - return vb2_create_bufs(&icd->vb2_vidq, create); + return -ENOTTY; + + if (icd->streamer && icd->streamer != file) + return -EBUSY; + + ret = vb2_create_bufs(&icd->vb2_vidq, create); + if (!ret) + icd->streamer = file; + return ret; } static int soc_camera_prepare_buf(struct file *file, void *priv, @@ -467,14 +476,13 @@ static int soc_camera_expbuf(struct file *file, void *priv, struct soc_camera_device *icd = file->private_data; struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - if (icd->streamer != file) - return -EBUSY; - /* videobuf2 only */ if (ici->ops->init_videobuf) - return -EINVAL; - else - return vb2_expbuf(&icd->vb2_vidq, p); + return -ENOTTY; + + if (icd->streamer && icd->streamer != file) + return -EBUSY; + return vb2_expbuf(&icd->vb2_vidq, p); } /* Always entered with .host_lock held */ @@ -780,20 +788,21 @@ static int soc_camera_close(struct file *file) struct soc_camera_host *ici = to_soc_camera_host(icd->parent); mutex_lock(&ici->host_lock); + if (icd->streamer == file) { + if (ici->ops->init_videobuf2) + vb2_queue_release(&icd->vb2_vidq); + icd->streamer = NULL; + } icd->use_count--; if (!icd->use_count) { pm_runtime_suspend(&icd->vdev->dev); pm_runtime_disable(&icd->vdev->dev); - if (ici->ops->init_videobuf2) - vb2_queue_release(&icd->vb2_vidq); __soc_camera_power_off(icd); soc_camera_remove_device(icd); } - if (icd->streamer == file) - icd->streamer = NULL; mutex_unlock(&ici->host_lock); module_put(ici->ops->owner); @@ -992,6 +1001,7 @@ static int soc_camera_streamoff(struct file *file, void *priv, struct soc_camera_device *icd = file->private_data; struct v4l2_subdev *sd = soc_camera_to_subdev(icd); struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + int ret; WARN_ON(priv != file->private_data); @@ -1006,13 +1016,13 @@ static int soc_camera_streamoff(struct file *file, void *priv, * remaining buffers. When the last buffer is freed, stop capture */ if (ici->ops->init_videobuf) - videobuf_streamoff(&icd->vb_vidq); + ret = videobuf_streamoff(&icd->vb_vidq); else - vb2_streamoff(&icd->vb2_vidq, i); + ret = vb2_streamoff(&icd->vb2_vidq, i); v4l2_subdev_call(sd, video, s_stream, 0); - return 0; + return ret; } static int soc_camera_cropcap(struct file *file, void *fh, diff --git a/drivers/media/platform/sti/bdisp/bdisp-debug.c b/drivers/media/platform/sti/bdisp/bdisp-debug.c index 18282a0f8..79c56356a 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-debug.c +++ b/drivers/media/platform/sti/bdisp/bdisp-debug.c @@ -116,6 +116,9 @@ static void bdisp_dbg_dump_tty(struct seq_file *s, u32 val) case BDISP_RGB565: seq_puts(s, "RGB565 - "); break; + case BDISP_RGB888: + seq_puts(s, "RGB888 - "); + break; case BDISP_XRGB8888: seq_puts(s, "xRGB888 - "); break; @@ -185,6 +188,9 @@ static void bdisp_dbg_dump_sty(struct seq_file *s, case BDISP_RGB565: seq_puts(s, "RGB565 - "); break; + case BDISP_RGB888: + seq_puts(s, "RGB888 - "); + break; case BDISP_XRGB8888: seq_puts(s, "xRGB888 - "); break; @@ -420,6 +426,8 @@ static const char *bdisp_fmt_to_str(struct bdisp_frame frame) return "NV12"; case V4L2_PIX_FMT_RGB565: return "RGB16"; + case V4L2_PIX_FMT_RGB24: + return "RGB24"; case V4L2_PIX_FMT_XBGR32: return "XRGB"; case V4L2_PIX_FMT_ABGR32: diff --git a/drivers/media/platform/sti/bdisp/bdisp-hw.c b/drivers/media/platform/sti/bdisp/bdisp-hw.c index 465828e85..052c932ac 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-hw.c +++ b/drivers/media/platform/sti/bdisp/bdisp-hw.c @@ -336,8 +336,8 @@ static int bdisp_hw_get_hv_inc(struct bdisp_ctx *ctx, u16 *h_inc, u16 *v_inc) src_w = ctx->src.crop.width; src_h = ctx->src.crop.height; - dst_w = ctx->dst.width; - dst_h = ctx->dst.height; + dst_w = ctx->dst.crop.width; + dst_h = ctx->dst.crop.height; if (bdisp_hw_get_inc(src_w, dst_w, h_inc) || bdisp_hw_get_inc(src_h, dst_h, v_inc)) { @@ -483,9 +483,9 @@ static void bdisp_hw_build_node(struct bdisp_ctx *ctx, src_rect.width -= src_x_offset; src_rect.width = min_t(__s32, MAX_SRC_WIDTH, src_rect.width); - dst_x_offset = (src_x_offset * dst->width) / ctx->src.crop.width; + dst_x_offset = (src_x_offset * dst_width) / ctx->src.crop.width; dst_rect.left += dst_x_offset; - dst_rect.width = (src_rect.width * dst->width) / ctx->src.crop.width; + dst_rect.width = (src_rect.width * dst_width) / ctx->src.crop.width; /* General */ src_fmt = src->fmt->pixelformat; @@ -768,12 +768,12 @@ static void bdisp_hw_save_request(struct bdisp_ctx *ctx) /* Allocate memory if not done yet */ if (!copy_node[i]) { copy_node[i] = devm_kzalloc(ctx->bdisp_dev->dev, - sizeof(*copy_node), + sizeof(*copy_node[i]), GFP_KERNEL); if (!copy_node[i]) return; } - copy_node[i] = node[i]; + *copy_node[i] = *node[i]; } } diff --git a/drivers/media/platform/sti/bdisp/bdisp-v4l2.c b/drivers/media/platform/sti/bdisp/bdisp-v4l2.c index 9e782ebe1..df61355b4 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-v4l2.c +++ b/drivers/media/platform/sti/bdisp/bdisp-v4l2.c @@ -851,33 +851,56 @@ static int bdisp_g_selection(struct file *file, void *fh, struct bdisp_frame *frame; struct bdisp_ctx *ctx = fh_to_ctx(fh); - if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { - /* Composing / capture is not supported */ - dev_dbg(ctx->bdisp_dev->dev, "Not supported for capture\n"); - return -EINVAL; - } - frame = ctx_get_frame(ctx, s->type); if (IS_ERR(frame)) { dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); return PTR_ERR(frame); } - switch (s->target) { - case V4L2_SEL_TGT_CROP: - /* cropped frame */ - s->r = frame->crop; + switch (s->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* cropped frame */ + s->r = frame->crop; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + /* complete frame */ + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->width; + s->r.height = frame->height; + break; + default: + dev_err(ctx->bdisp_dev->dev, "Invalid target\n"); + return -EINVAL; + } break; - case V4L2_SEL_TGT_CROP_DEFAULT: - case V4L2_SEL_TGT_CROP_BOUNDS: - /* complete frame */ - s->r.left = 0; - s->r.top = 0; - s->r.width = frame->width; - s->r.height = frame->height; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_PADDED: + /* composed (cropped) frame */ + s->r = frame->crop; + break; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* complete frame */ + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->width; + s->r.height = frame->height; + break; + default: + dev_err(ctx->bdisp_dev->dev, "Invalid target\n"); + return -EINVAL; + } break; + default: - dev_dbg(ctx->bdisp_dev->dev, "Invalid target\n"); + dev_err(ctx->bdisp_dev->dev, "Invalid type\n"); return -EINVAL; } @@ -906,15 +929,18 @@ static int bdisp_s_selection(struct file *file, void *fh, struct bdisp_frame *frame; struct bdisp_ctx *ctx = fh_to_ctx(fh); struct v4l2_rect *in, out; + bool valid = false; - if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { - /* Composing / capture is not supported */ - dev_dbg(ctx->bdisp_dev->dev, "Not supported for capture\n"); - return -EINVAL; - } + if ((s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) && + (s->target == V4L2_SEL_TGT_CROP)) + valid = true; + + if ((s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->target == V4L2_SEL_TGT_COMPOSE)) + valid = true; - if (s->target != V4L2_SEL_TGT_CROP) { - dev_dbg(ctx->bdisp_dev->dev, "Invalid target\n"); + if (!valid) { + dev_err(ctx->bdisp_dev->dev, "Invalid type / target\n"); return -EINVAL; } diff --git a/drivers/media/platform/sti/c8sectpfe/Kconfig b/drivers/media/platform/sti/c8sectpfe/Kconfig new file mode 100644 index 000000000..641ad8f34 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/Kconfig @@ -0,0 +1,28 @@ +config DVB_C8SECTPFE + tristate "STMicroelectronics C8SECTPFE DVB support" + depends on PINCTRL && DVB_CORE && I2C + depends on ARCH_STI || ARCH_MULTIPLATFORM || COMPILE_TEST + select FW_LOADER + select FW_LOADER_USER_HELPER_FALLBACK + select DEBUG_FS + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT + + ---help--- + This adds support for DVB front-end cards connected + to TS inputs of STiH407/410 SoC. + + The driver currently supports C8SECTPFE's TS input block, + memdma engine, and HW PID filtering. + + Supported DVB front-end cards are: + - STMicroelectronics DVB-T B2100A (STV0367 + TDA18212) + - STMicroelectronics DVB-S/S2 STV0903 + STV6110 + LNBP24 board + + To compile this driver as a module, choose M here: the + module will be called c8sectpfe. diff --git a/drivers/media/platform/sti/c8sectpfe/Makefile b/drivers/media/platform/sti/c8sectpfe/Makefile new file mode 100644 index 000000000..b578c7cb4 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/Makefile @@ -0,0 +1,9 @@ +c8sectpfe-y += c8sectpfe-core.o c8sectpfe-common.o c8sectpfe-dvb.o \ + c8sectpfe-debugfs.o + +obj-$(CONFIG_DVB_C8SECTPFE) += c8sectpfe.o + +ccflags-y += -Idrivers/media/i2c +ccflags-y += -Idrivers/media/common +ccflags-y += -Idrivers/media/dvb-core/ -Idrivers/media/dvb-frontends/ \ + -Idrivers/media/tuners/ diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-common.c b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-common.c new file mode 100644 index 000000000..95223ab71 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-common.c @@ -0,0 +1,265 @@ +/* + * c8sectpfe-common.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dvb/dmx.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> + +#include "dmxdev.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +#include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" +#include "c8sectpfe-dvb.h" + +static int register_dvb(struct stdemux *demux, struct dvb_adapter *adap, + void *start_feed, void *stop_feed, + struct c8sectpfei *fei) +{ + int result; + + demux->dvb_demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + demux->dvb_demux.priv = demux; + demux->dvb_demux.filternum = C8SECTPFE_MAXCHANNEL; + demux->dvb_demux.feednum = C8SECTPFE_MAXCHANNEL; + + demux->dvb_demux.start_feed = start_feed; + demux->dvb_demux.stop_feed = stop_feed; + demux->dvb_demux.write_to_decoder = NULL; + + result = dvb_dmx_init(&demux->dvb_demux); + if (result < 0) { + dev_err(fei->dev, "dvb_dmx_init failed (errno = %d)\n", + result); + goto err_dmx; + } + + demux->dmxdev.filternum = demux->dvb_demux.filternum; + demux->dmxdev.demux = &demux->dvb_demux.dmx; + demux->dmxdev.capabilities = 0; + + result = dvb_dmxdev_init(&demux->dmxdev, adap); + if (result < 0) { + dev_err(fei->dev, "dvb_dmxdev_init failed (errno = %d)\n", + result); + + goto err_dmxdev; + } + + demux->hw_frontend.source = DMX_FRONTEND_0 + demux->tsin_index; + + result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + if (result < 0) { + dev_err(fei->dev, "add_frontend failed (errno = %d)\n", result); + goto err_fe_hw; + } + + demux->mem_frontend.source = DMX_MEMORY_FE; + result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); + if (result < 0) { + dev_err(fei->dev, "add_frontend failed (%d)\n", result); + goto err_fe_mem; + } + + result = demux->dvb_demux.dmx.connect_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + if (result < 0) { + dev_err(fei->dev, "connect_frontend (%d)\n", result); + goto err_fe_con; + } + + return 0; + +err_fe_con: + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); +err_fe_mem: + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); +err_fe_hw: + dvb_dmxdev_release(&demux->dmxdev); +err_dmxdev: + dvb_dmx_release(&demux->dvb_demux); +err_dmx: + return result; + +} + +static void unregister_dvb(struct stdemux *demux) +{ + + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); + + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + + dvb_dmxdev_release(&demux->dmxdev); + + dvb_dmx_release(&demux->dvb_demux); +} + +static struct c8sectpfe *c8sectpfe_create(struct c8sectpfei *fei, + void *start_feed, + void *stop_feed) +{ + struct c8sectpfe *c8sectpfe; + int result; + int i, j; + + short int ids[] = { -1 }; + + c8sectpfe = kzalloc(sizeof(struct c8sectpfe), GFP_KERNEL); + if (!c8sectpfe) + goto err1; + + mutex_init(&c8sectpfe->lock); + + c8sectpfe->device = fei->dev; + + result = dvb_register_adapter(&c8sectpfe->adapter, "STi c8sectpfe", + THIS_MODULE, fei->dev, ids); + if (result < 0) { + dev_err(fei->dev, "dvb_register_adapter failed (errno = %d)\n", + result); + goto err2; + } + + c8sectpfe->adapter.priv = fei; + + for (i = 0; i < fei->tsin_count; i++) { + + c8sectpfe->demux[i].tsin_index = i; + c8sectpfe->demux[i].c8sectpfei = fei; + + result = register_dvb(&c8sectpfe->demux[i], &c8sectpfe->adapter, + start_feed, stop_feed, fei); + if (result < 0) { + dev_err(fei->dev, + "register_dvb feed=%d failed (errno = %d)\n", + result, i); + + /* we take a all or nothing approach */ + for (j = 0; j < i; j++) + unregister_dvb(&c8sectpfe->demux[j]); + goto err3; + } + } + + c8sectpfe->num_feeds = fei->tsin_count; + + return c8sectpfe; +err3: + dvb_unregister_adapter(&c8sectpfe->adapter); +err2: + kfree(c8sectpfe); +err1: + return NULL; +}; + +static void c8sectpfe_delete(struct c8sectpfe *c8sectpfe) +{ + int i; + + if (!c8sectpfe) + return; + + for (i = 0; i < c8sectpfe->num_feeds; i++) + unregister_dvb(&c8sectpfe->demux[i]); + + dvb_unregister_adapter(&c8sectpfe->adapter); + + kfree(c8sectpfe); +}; + +void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe, + struct c8sectpfei *fei) +{ + int n; + struct channel_info *tsin; + + for (n = 0; n < fei->tsin_count; n++) { + + tsin = fei->channel_data[n]; + + if (tsin && tsin->frontend) { + dvb_unregister_frontend(tsin->frontend); + dvb_frontend_detach(tsin->frontend); + } + + if (tsin && tsin->i2c_adapter) + i2c_put_adapter(tsin->i2c_adapter); + + if (tsin && tsin->i2c_client) { + if (tsin->i2c_client->dev.driver->owner) + module_put(tsin->i2c_client->dev.driver->owner); + i2c_unregister_device(tsin->i2c_client); + } + } + + c8sectpfe_delete(c8sectpfe); +}; + +int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe, + struct c8sectpfei *fei, + void *start_feed, + void *stop_feed) +{ + struct channel_info *tsin; + struct dvb_frontend *frontend; + int n, res; + + *c8sectpfe = c8sectpfe_create(fei, start_feed, stop_feed); + if (!*c8sectpfe) + return -ENOMEM; + + for (n = 0; n < fei->tsin_count; n++) { + tsin = fei->channel_data[n]; + + res = c8sectpfe_frontend_attach(&frontend, *c8sectpfe, tsin, n); + if (res) + goto err; + + res = dvb_register_frontend(&c8sectpfe[0]->adapter, frontend); + if (res < 0) { + dev_err(fei->dev, "dvb_register_frontend failed (%d)\n", + res); + goto err; + } + + tsin->frontend = frontend; + } + + return 0; + +err: + c8sectpfe_tuner_unregister_frontend(*c8sectpfe, fei); + return res; +} diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-common.h b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-common.h new file mode 100644 index 000000000..da21c0ac0 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-common.h @@ -0,0 +1,64 @@ +/* + * c8sectpfe-common.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#ifndef _C8SECTPFE_COMMON_H_ +#define _C8SECTPFE_COMMON_H_ + +#include <linux/dvb/dmx.h> +#include <linux/dvb/frontend.h> +#include <linux/gpio.h> +#include <linux/version.h> + +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +/* Maximum number of channels */ +#define C8SECTPFE_MAXADAPTER (4) +#define C8SECTPFE_MAXCHANNEL 64 +#define STPTI_MAXCHANNEL 64 + +#define MAX_INPUTBLOCKS 7 + +struct c8sectpfe; +struct stdemux; + +struct stdemux { + struct dvb_demux dvb_demux; + struct dmxdev dmxdev; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + int tsin_index; + int running_feed_count; + struct c8sectpfei *c8sectpfei; +}; + +struct c8sectpfe { + struct stdemux demux[MAX_INPUTBLOCKS]; + struct mutex lock; + struct dvb_adapter adapter; + struct device *device; + int mapping; + int num_feeds; +}; + +/* Channel registration */ +int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe, + struct c8sectpfei *fei, + void *start_feed, + void *stop_feed); + +void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe, + struct c8sectpfei *fei); + +#endif diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-core.c b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-core.c new file mode 100644 index 000000000..ab969b7a4 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-core.c @@ -0,0 +1,1235 @@ +/* + * c8sectpfe-core.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/dvb/dmx.h> +#include <linux/dvb/frontend.h> +#include <linux/errno.h> +#include <linux/firmware.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/version.h> +#include <linux/wait.h> +#include <linux/pinctrl/pinctrl.h> + +#include "c8sectpfe-core.h" +#include "c8sectpfe-common.h" +#include "c8sectpfe-debugfs.h" +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" + +#define FIRMWARE_MEMDMA "/*(DEBLOBBED)*/" +/*(DEBLOBBED)*/ + +#define PID_TABLE_SIZE 1024 +#define POLL_MSECS 50 + +static int load_c8sectpfe_fw_step1(struct c8sectpfei *fei); + +#define TS_PKT_SIZE 188 +#define HEADER_SIZE (4) +#define PACKET_SIZE (TS_PKT_SIZE+HEADER_SIZE) + +#define FEI_ALIGNMENT (32) +/* hw requires minimum of 8*PACKET_SIZE and padded to 8byte boundary */ +#define FEI_BUFFER_SIZE (8*PACKET_SIZE*340) + +#define FIFO_LEN 1024 + +static void c8sectpfe_timer_interrupt(unsigned long ac8sectpfei) +{ + struct c8sectpfei *fei = (struct c8sectpfei *)ac8sectpfei; + struct channel_info *channel; + int chan_num; + + /* iterate through input block channels */ + for (chan_num = 0; chan_num < fei->tsin_count; chan_num++) { + channel = fei->channel_data[chan_num]; + + /* is this descriptor initialised and TP enabled */ + if (channel->irec && readl(channel->irec + DMA_PRDS_TPENABLE)) + tasklet_schedule(&channel->tsklet); + } + + fei->timer.expires = jiffies + msecs_to_jiffies(POLL_MSECS); + add_timer(&fei->timer); +} + +static void channel_swdemux_tsklet(unsigned long data) +{ + struct channel_info *channel = (struct channel_info *)data; + struct c8sectpfei *fei = channel->fei; + unsigned long wp, rp; + int pos, num_packets, n, size; + u8 *buf; + + if (unlikely(!channel || !channel->irec)) + return; + + wp = readl(channel->irec + DMA_PRDS_BUSWP_TP(0)); + rp = readl(channel->irec + DMA_PRDS_BUSRP_TP(0)); + + pos = rp - channel->back_buffer_busaddr; + + /* has it wrapped */ + if (wp < rp) + wp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE; + + size = wp - rp; + num_packets = size / PACKET_SIZE; + + /* manage cache so data is visible to CPU */ + dma_sync_single_for_cpu(fei->dev, + rp, + size, + DMA_FROM_DEVICE); + + buf = (u8 *) channel->back_buffer_aligned; + + dev_dbg(fei->dev, + "chan=%d channel=%p num_packets = %d, buf = %p, pos = 0x%x\n\t" + "rp=0x%lx, wp=0x%lx\n", + channel->tsin_id, channel, num_packets, buf, pos, rp, wp); + + for (n = 0; n < num_packets; n++) { + dvb_dmx_swfilter_packets( + &fei->c8sectpfe[0]-> + demux[channel->demux_mapping].dvb_demux, + &buf[pos], 1); + + pos += PACKET_SIZE; + } + + /* advance the read pointer */ + if (wp == (channel->back_buffer_busaddr + FEI_BUFFER_SIZE)) + writel(channel->back_buffer_busaddr, channel->irec + + DMA_PRDS_BUSRP_TP(0)); + else + writel(wp, channel->irec + DMA_PRDS_BUSWP_TP(0)); +} + +static int c8sectpfe_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct stdemux *stdemux = (struct stdemux *)demux->priv; + struct c8sectpfei *fei = stdemux->c8sectpfei; + struct channel_info *channel; + u32 tmp; + unsigned long *bitmap; + + switch (dvbdmxfeed->type) { + case DMX_TYPE_TS: + break; + case DMX_TYPE_SEC: + break; + default: + dev_err(fei->dev, "%s:%d Error bailing\n" + , __func__, __LINE__); + return -EINVAL; + } + + if (dvbdmxfeed->type == DMX_TYPE_TS) { + switch (dvbdmxfeed->pes_type) { + case DMX_PES_VIDEO: + case DMX_PES_AUDIO: + case DMX_PES_TELETEXT: + case DMX_PES_PCR: + case DMX_PES_OTHER: + break; + default: + dev_err(fei->dev, "%s:%d Error bailing\n" + , __func__, __LINE__); + return -EINVAL; + } + } + + if (!atomic_read(&fei->fw_loaded)) { + dev_err(fei->dev, "%s: c8sectpfe fw not loaded\n", __func__); + return -EINVAL; + } + + mutex_lock(&fei->lock); + + channel = fei->channel_data[stdemux->tsin_index]; + + bitmap = (unsigned long *) channel->pid_buffer_aligned; + + /* 8192 is a special PID */ + if (dvbdmxfeed->pid == 8192) { + tmp = readl(fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + tmp &= ~C8SECTPFE_PID_ENABLE; + writel(tmp, fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + + } else { + bitmap_set(bitmap, dvbdmxfeed->pid, 1); + } + + /* manage cache so PID bitmap is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + channel->active = 1; + + if (fei->global_feed_count == 0) { + fei->timer.expires = jiffies + + msecs_to_jiffies(msecs_to_jiffies(POLL_MSECS)); + + add_timer(&fei->timer); + } + + if (stdemux->running_feed_count == 0) { + + dev_dbg(fei->dev, "Starting channel=%p\n", channel); + + tasklet_init(&channel->tsklet, channel_swdemux_tsklet, + (unsigned long) channel); + + /* Reset the internal inputblock sram pointers */ + writel(channel->fifo, + fei->io + C8SECTPFE_IB_BUFF_STRT(channel->tsin_id)); + writel(channel->fifo + FIFO_LEN - 1, + fei->io + C8SECTPFE_IB_BUFF_END(channel->tsin_id)); + + writel(channel->fifo, + fei->io + C8SECTPFE_IB_READ_PNT(channel->tsin_id)); + writel(channel->fifo, + fei->io + C8SECTPFE_IB_WRT_PNT(channel->tsin_id)); + + + /* reset read / write memdma ptrs for this channel */ + writel(channel->back_buffer_busaddr, channel->irec + + DMA_PRDS_BUSBASE_TP(0)); + + tmp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, channel->irec + DMA_PRDS_BUSTOP_TP(0)); + + writel(channel->back_buffer_busaddr, channel->irec + + DMA_PRDS_BUSWP_TP(0)); + + /* Issue a reset and enable InputBlock */ + writel(C8SECTPFE_SYS_ENABLE | C8SECTPFE_SYS_RESET + , fei->io + C8SECTPFE_IB_SYS(channel->tsin_id)); + + /* and enable the tp */ + writel(0x1, channel->irec + DMA_PRDS_TPENABLE); + + dev_dbg(fei->dev, "%s:%d Starting DMA feed on stdemux=%p\n" + , __func__, __LINE__, stdemux); + } + + stdemux->running_feed_count++; + fei->global_feed_count++; + + mutex_unlock(&fei->lock); + + return 0; +} + +static int c8sectpfe_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + + struct dvb_demux *demux = dvbdmxfeed->demux; + struct stdemux *stdemux = (struct stdemux *)demux->priv; + struct c8sectpfei *fei = stdemux->c8sectpfei; + struct channel_info *channel; + int idlereq; + u32 tmp; + int ret; + unsigned long *bitmap; + + if (!atomic_read(&fei->fw_loaded)) { + dev_err(fei->dev, "%s: c8sectpfe fw not loaded\n", __func__); + return -EINVAL; + } + + mutex_lock(&fei->lock); + + channel = fei->channel_data[stdemux->tsin_index]; + + bitmap = (unsigned long *) channel->pid_buffer_aligned; + + if (dvbdmxfeed->pid == 8192) { + tmp = readl(fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + tmp |= C8SECTPFE_PID_ENABLE; + writel(tmp, fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + } else { + bitmap_clear(bitmap, dvbdmxfeed->pid, 1); + } + + /* manage cache so data is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + if (--stdemux->running_feed_count == 0) { + + channel = fei->channel_data[stdemux->tsin_index]; + + /* TP re-configuration on page 168 of functional spec */ + + /* disable IB (prevents more TS data going to memdma) */ + writel(0, fei->io + C8SECTPFE_IB_SYS(channel->tsin_id)); + + /* disable this channels descriptor */ + writel(0, channel->irec + DMA_PRDS_TPENABLE); + + tasklet_disable(&channel->tsklet); + + /* now request memdma channel goes idle */ + idlereq = (1 << channel->tsin_id) | IDLEREQ; + writel(idlereq, fei->io + DMA_IDLE_REQ); + + /* wait for idle irq handler to signal completion */ + ret = wait_for_completion_timeout(&channel->idle_completion, + msecs_to_jiffies(100)); + + if (ret == 0) + dev_warn(fei->dev, + "Timeout waiting for idle irq on tsin%d\n", + channel->tsin_id); + + reinit_completion(&channel->idle_completion); + + /* reset read / write ptrs for this channel */ + + writel(channel->back_buffer_busaddr, + channel->irec + DMA_PRDS_BUSBASE_TP(0)); + + tmp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, channel->irec + DMA_PRDS_BUSTOP_TP(0)); + + writel(channel->back_buffer_busaddr, + channel->irec + DMA_PRDS_BUSWP_TP(0)); + + dev_dbg(fei->dev, + "%s:%d stopping DMA feed on stdemux=%p channel=%d\n", + __func__, __LINE__, stdemux, channel->tsin_id); + + /* turn off all PIDS in the bitmap */ + memset((void *)channel->pid_buffer_aligned + , 0x00, PID_TABLE_SIZE); + + /* manage cache so data is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + channel->active = 0; + } + + if (--fei->global_feed_count == 0) { + dev_dbg(fei->dev, "%s:%d global_feed_count=%d\n" + , __func__, __LINE__, fei->global_feed_count); + + del_timer(&fei->timer); + } + + mutex_unlock(&fei->lock); + + return 0; +} + +static struct channel_info *find_channel(struct c8sectpfei *fei, int tsin_num) +{ + int i; + + for (i = 0; i < C8SECTPFE_MAX_TSIN_CHAN; i++) { + if (!fei->channel_data[i]) + continue; + + if (fei->channel_data[i]->tsin_id == tsin_num) + return fei->channel_data[i]; + } + + return NULL; +} + +static void c8sectpfe_getconfig(struct c8sectpfei *fei) +{ + struct c8sectpfe_hw *hw = &fei->hw_stats; + + hw->num_ib = readl(fei->io + SYS_CFG_NUM_IB); + hw->num_mib = readl(fei->io + SYS_CFG_NUM_MIB); + hw->num_swts = readl(fei->io + SYS_CFG_NUM_SWTS); + hw->num_tsout = readl(fei->io + SYS_CFG_NUM_TSOUT); + hw->num_ccsc = readl(fei->io + SYS_CFG_NUM_CCSC); + hw->num_ram = readl(fei->io + SYS_CFG_NUM_RAM); + hw->num_tp = readl(fei->io + SYS_CFG_NUM_TP); + + dev_info(fei->dev, "C8SECTPFE hw supports the following:\n"); + dev_info(fei->dev, "Input Blocks: %d\n", hw->num_ib); + dev_info(fei->dev, "Merged Input Blocks: %d\n", hw->num_mib); + dev_info(fei->dev, "Software Transport Stream Inputs: %d\n" + , hw->num_swts); + dev_info(fei->dev, "Transport Stream Output: %d\n", hw->num_tsout); + dev_info(fei->dev, "Cable Card Converter: %d\n", hw->num_ccsc); + dev_info(fei->dev, "RAMs supported by C8SECTPFE: %d\n", hw->num_ram); + dev_info(fei->dev, "Tango TPs supported by C8SECTPFE: %d\n" + , hw->num_tp); +} + +static irqreturn_t c8sectpfe_idle_irq_handler(int irq, void *priv) +{ + struct c8sectpfei *fei = priv; + struct channel_info *chan; + int bit; + unsigned long tmp = readl(fei->io + DMA_IDLE_REQ); + + /* page 168 of functional spec: Clear the idle request + by writing 0 to the C8SECTPFE_DMA_IDLE_REQ register. */ + + /* signal idle completion */ + for_each_set_bit(bit, &tmp, fei->hw_stats.num_ib) { + + chan = find_channel(fei, bit); + + if (chan) + complete(&chan->idle_completion); + } + + writel(0, fei->io + DMA_IDLE_REQ); + + return IRQ_HANDLED; +} + + +static void free_input_block(struct c8sectpfei *fei, struct channel_info *tsin) +{ + if (!fei || !tsin) + return; + + if (tsin->back_buffer_busaddr) + if (!dma_mapping_error(fei->dev, tsin->back_buffer_busaddr)) + dma_unmap_single(fei->dev, tsin->back_buffer_busaddr, + FEI_BUFFER_SIZE, DMA_BIDIRECTIONAL); + + kfree(tsin->back_buffer_start); + + if (tsin->pid_buffer_busaddr) + if (!dma_mapping_error(fei->dev, tsin->pid_buffer_busaddr)) + dma_unmap_single(fei->dev, tsin->pid_buffer_busaddr, + PID_TABLE_SIZE, DMA_BIDIRECTIONAL); + + kfree(tsin->pid_buffer_start); +} + +#define MAX_NAME 20 + +static int configure_memdma_and_inputblock(struct c8sectpfei *fei, + struct channel_info *tsin) +{ + int ret; + u32 tmp; + char tsin_pin_name[MAX_NAME]; + + if (!fei || !tsin) + return -EINVAL; + + dev_dbg(fei->dev, "%s:%d Configuring channel=%p tsin=%d\n" + , __func__, __LINE__, tsin, tsin->tsin_id); + + init_completion(&tsin->idle_completion); + + tsin->back_buffer_start = kzalloc(FEI_BUFFER_SIZE + + FEI_ALIGNMENT, GFP_KERNEL); + + if (!tsin->back_buffer_start) { + ret = -ENOMEM; + goto err_unmap; + } + + /* Ensure backbuffer is 32byte aligned */ + tsin->back_buffer_aligned = tsin->back_buffer_start + + FEI_ALIGNMENT; + + tsin->back_buffer_aligned = (void *) + (((uintptr_t) tsin->back_buffer_aligned) & ~0x1F); + + tsin->back_buffer_busaddr = dma_map_single(fei->dev, + (void *)tsin->back_buffer_aligned, + FEI_BUFFER_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(fei->dev, tsin->back_buffer_busaddr)) { + dev_err(fei->dev, "failed to map back_buffer\n"); + ret = -EFAULT; + goto err_unmap; + } + + /* + * The pid buffer can be configured (in hw) for byte or bit + * per pid. By powers of deduction we conclude stih407 family + * is configured (at SoC design stage) for bit per pid. + */ + tsin->pid_buffer_start = kzalloc(2048, GFP_KERNEL); + + if (!tsin->pid_buffer_start) { + ret = -ENOMEM; + goto err_unmap; + } + + /* + * PID buffer needs to be aligned to size of the pid table + * which at bit per pid is 1024 bytes (8192 pids / 8). + * PIDF_BASE register enforces this alignment when writing + * the register. + */ + + tsin->pid_buffer_aligned = tsin->pid_buffer_start + + PID_TABLE_SIZE; + + tsin->pid_buffer_aligned = (void *) + (((uintptr_t) tsin->pid_buffer_aligned) & ~0x3ff); + + tsin->pid_buffer_busaddr = dma_map_single(fei->dev, + tsin->pid_buffer_aligned, + PID_TABLE_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(fei->dev, tsin->pid_buffer_busaddr)) { + dev_err(fei->dev, "failed to map pid_bitmap\n"); + ret = -EFAULT; + goto err_unmap; + } + + /* manage cache so pid bitmap is visible to HW */ + dma_sync_single_for_device(fei->dev, + tsin->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + snprintf(tsin_pin_name, MAX_NAME, "tsin%d-%s", tsin->tsin_id, + (tsin->serial_not_parallel ? "serial" : "parallel")); + + tsin->pstate = pinctrl_lookup_state(fei->pinctrl, tsin_pin_name); + if (IS_ERR(tsin->pstate)) { + dev_err(fei->dev, "%s: pinctrl_lookup_state couldn't find %s state\n" + , __func__, tsin_pin_name); + ret = PTR_ERR(tsin->pstate); + goto err_unmap; + } + + ret = pinctrl_select_state(fei->pinctrl, tsin->pstate); + + if (ret) { + dev_err(fei->dev, "%s: pinctrl_select_state failed\n" + , __func__); + goto err_unmap; + } + + /* Enable this input block */ + tmp = readl(fei->io + SYS_INPUT_CLKEN); + tmp |= BIT(tsin->tsin_id); + writel(tmp, fei->io + SYS_INPUT_CLKEN); + + if (tsin->serial_not_parallel) + tmp |= C8SECTPFE_SERIAL_NOT_PARALLEL; + + if (tsin->invert_ts_clk) + tmp |= C8SECTPFE_INVERT_TSCLK; + + if (tsin->async_not_sync) + tmp |= C8SECTPFE_ASYNC_NOT_SYNC; + + tmp |= C8SECTPFE_ALIGN_BYTE_SOP | C8SECTPFE_BYTE_ENDIANNESS_MSB; + + writel(tmp, fei->io + C8SECTPFE_IB_IP_FMT_CFG(tsin->tsin_id)); + + writel(C8SECTPFE_SYNC(0x9) | + C8SECTPFE_DROP(0x9) | + C8SECTPFE_TOKEN(0x47), + fei->io + C8SECTPFE_IB_SYNCLCKDRP_CFG(tsin->tsin_id)); + + writel(TS_PKT_SIZE, fei->io + C8SECTPFE_IB_PKT_LEN(tsin->tsin_id)); + + /* Place the FIFO's at the end of the irec descriptors */ + + tsin->fifo = (tsin->tsin_id * FIFO_LEN); + + writel(tsin->fifo, fei->io + C8SECTPFE_IB_BUFF_STRT(tsin->tsin_id)); + writel(tsin->fifo + FIFO_LEN - 1, + fei->io + C8SECTPFE_IB_BUFF_END(tsin->tsin_id)); + + writel(tsin->fifo, fei->io + C8SECTPFE_IB_READ_PNT(tsin->tsin_id)); + writel(tsin->fifo, fei->io + C8SECTPFE_IB_WRT_PNT(tsin->tsin_id)); + + writel(tsin->pid_buffer_busaddr, + fei->io + PIDF_BASE(tsin->tsin_id)); + + dev_info(fei->dev, "chan=%d PIDF_BASE=0x%x pid_bus_addr=%pad\n", + tsin->tsin_id, readl(fei->io + PIDF_BASE(tsin->tsin_id)), + &tsin->pid_buffer_busaddr); + + /* Configure and enable HW PID filtering */ + + /* + * The PID value is created by assembling the first 8 bytes of + * the TS packet into a 64-bit word in big-endian format. A + * slice of that 64-bit word is taken from + * (PID_OFFSET+PID_NUM_BITS-1) to PID_OFFSET. + */ + tmp = (C8SECTPFE_PID_ENABLE | C8SECTPFE_PID_NUMBITS(13) + | C8SECTPFE_PID_OFFSET(40)); + + writel(tmp, fei->io + C8SECTPFE_IB_PID_SET(tsin->tsin_id)); + + dev_dbg(fei->dev, "chan=%d setting wp: %d, rp: %d, buf: %d-%d\n", + tsin->tsin_id, + readl(fei->io + C8SECTPFE_IB_WRT_PNT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_READ_PNT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_BUFF_STRT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_BUFF_END(tsin->tsin_id))); + + /* Get base addpress of pointer record block from DMEM */ + tsin->irec = fei->io + DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + + readl(fei->io + DMA_PTRREC_BASE); + + /* fill out pointer record data structure */ + + /* advance pointer record block to our channel */ + tsin->irec += (tsin->tsin_id * DMA_PRDS_SIZE); + + writel(tsin->fifo, tsin->irec + DMA_PRDS_MEMBASE); + + writel(tsin->fifo + FIFO_LEN - 1, tsin->irec + DMA_PRDS_MEMTOP); + + writel((188 + 7)&~7, tsin->irec + DMA_PRDS_PKTSIZE); + + writel(0x1, tsin->irec + DMA_PRDS_TPENABLE); + + /* read/write pointers with physical bus address */ + + writel(tsin->back_buffer_busaddr, tsin->irec + DMA_PRDS_BUSBASE_TP(0)); + + tmp = tsin->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, tsin->irec + DMA_PRDS_BUSTOP_TP(0)); + + writel(tsin->back_buffer_busaddr, tsin->irec + DMA_PRDS_BUSWP_TP(0)); + writel(tsin->back_buffer_busaddr, tsin->irec + DMA_PRDS_BUSRP_TP(0)); + + /* initialize tasklet */ + tasklet_init(&tsin->tsklet, channel_swdemux_tsklet, + (unsigned long) tsin); + + return 0; + +err_unmap: + free_input_block(fei, tsin); + return ret; +} + +static irqreturn_t c8sectpfe_error_irq_handler(int irq, void *priv) +{ + struct c8sectpfei *fei = priv; + + dev_err(fei->dev, "%s: error handling not yet implemented\n" + , __func__); + + /* + * TODO FIXME we should detect some error conditions here + * and ideally so something about them! + */ + + return IRQ_HANDLED; +} + +static int c8sectpfe_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node; + struct c8sectpfei *fei; + struct resource *res; + int ret, index = 0; + struct channel_info *tsin; + + /* Allocate the c8sectpfei structure */ + fei = devm_kzalloc(dev, sizeof(struct c8sectpfei), GFP_KERNEL); + if (!fei) + return -ENOMEM; + + fei->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "c8sectpfe"); + fei->io = devm_ioremap_resource(dev, res); + if (IS_ERR(fei->io)) + return PTR_ERR(fei->io); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "c8sectpfe-ram"); + fei->sram = devm_ioremap_resource(dev, res); + if (IS_ERR(fei->sram)) + return PTR_ERR(fei->sram); + + fei->sram_size = res->end - res->start; + + fei->idle_irq = platform_get_irq_byname(pdev, "c8sectpfe-idle-irq"); + if (fei->idle_irq < 0) { + dev_err(dev, "Can't get c8sectpfe-idle-irq\n"); + return fei->idle_irq; + } + + fei->error_irq = platform_get_irq_byname(pdev, "c8sectpfe-error-irq"); + if (fei->error_irq < 0) { + dev_err(dev, "Can't get c8sectpfe-error-irq\n"); + return fei->error_irq; + } + + platform_set_drvdata(pdev, fei); + + fei->c8sectpfeclk = devm_clk_get(dev, "c8sectpfe"); + if (IS_ERR(fei->c8sectpfeclk)) { + dev_err(dev, "c8sectpfe clk not found\n"); + return PTR_ERR(fei->c8sectpfeclk); + } + + ret = clk_prepare_enable(fei->c8sectpfeclk); + if (ret) { + dev_err(dev, "Failed to enable c8sectpfe clock\n"); + return ret; + } + + /* to save power disable all IP's (on by default) */ + writel(0, fei->io + SYS_INPUT_CLKEN); + + /* Enable memdma clock */ + writel(MEMDMAENABLE, fei->io + SYS_OTHER_CLKEN); + + /* clear internal sram */ + memset_io(fei->sram, 0x0, fei->sram_size); + + c8sectpfe_getconfig(fei); + + ret = devm_request_irq(dev, fei->idle_irq, c8sectpfe_idle_irq_handler, + 0, "c8sectpfe-idle-irq", fei); + if (ret) { + dev_err(dev, "Can't register c8sectpfe-idle-irq IRQ.\n"); + goto err_clk_disable; + } + + ret = devm_request_irq(dev, fei->error_irq, + c8sectpfe_error_irq_handler, 0, + "c8sectpfe-error-irq", fei); + if (ret) { + dev_err(dev, "Can't register c8sectpfe-error-irq IRQ.\n"); + goto err_clk_disable; + } + + fei->tsin_count = of_get_child_count(np); + + if (fei->tsin_count > C8SECTPFE_MAX_TSIN_CHAN || + fei->tsin_count > fei->hw_stats.num_ib) { + + dev_err(dev, "More tsin declared than exist on SoC!\n"); + ret = -EINVAL; + goto err_clk_disable; + } + + fei->pinctrl = devm_pinctrl_get(dev); + + if (IS_ERR(fei->pinctrl)) { + dev_err(dev, "Error getting tsin pins\n"); + ret = PTR_ERR(fei->pinctrl); + goto err_clk_disable; + } + + for_each_child_of_node(np, child) { + struct device_node *i2c_bus; + + fei->channel_data[index] = devm_kzalloc(dev, + sizeof(struct channel_info), + GFP_KERNEL); + + if (!fei->channel_data[index]) { + ret = -ENOMEM; + goto err_clk_disable; + } + + tsin = fei->channel_data[index]; + + tsin->fei = fei; + + ret = of_property_read_u32(child, "tsin-num", &tsin->tsin_id); + if (ret) { + dev_err(&pdev->dev, "No tsin_num found\n"); + goto err_clk_disable; + } + + /* sanity check value */ + if (tsin->tsin_id > fei->hw_stats.num_ib) { + dev_err(&pdev->dev, + "tsin-num %d specified greater than number\n\t" + "of input block hw in SoC! (%d)", + tsin->tsin_id, fei->hw_stats.num_ib); + ret = -EINVAL; + goto err_clk_disable; + } + + tsin->invert_ts_clk = of_property_read_bool(child, + "invert-ts-clk"); + + tsin->serial_not_parallel = of_property_read_bool(child, + "serial-not-parallel"); + + tsin->async_not_sync = of_property_read_bool(child, + "async-not-sync"); + + ret = of_property_read_u32(child, "dvb-card", + &tsin->dvb_card); + if (ret) { + dev_err(&pdev->dev, "No dvb-card found\n"); + goto err_clk_disable; + } + + i2c_bus = of_parse_phandle(child, "i2c-bus", 0); + if (!i2c_bus) { + dev_err(&pdev->dev, "No i2c-bus found\n"); + goto err_clk_disable; + } + tsin->i2c_adapter = + of_find_i2c_adapter_by_node(i2c_bus); + if (!tsin->i2c_adapter) { + dev_err(&pdev->dev, "No i2c adapter found\n"); + of_node_put(i2c_bus); + goto err_clk_disable; + } + of_node_put(i2c_bus); + + tsin->rst_gpio = of_get_named_gpio(child, "rst-gpio", 0); + + ret = gpio_is_valid(tsin->rst_gpio); + if (!ret) { + dev_err(dev, + "reset gpio for tsin%d not valid (gpio=%d)\n", + tsin->tsin_id, tsin->rst_gpio); + goto err_clk_disable; + } + + ret = devm_gpio_request_one(dev, tsin->rst_gpio, + GPIOF_OUT_INIT_LOW, "NIM reset"); + if (ret && ret != -EBUSY) { + dev_err(dev, "Can't request tsin%d reset gpio\n" + , fei->channel_data[index]->tsin_id); + goto err_clk_disable; + } + + if (!ret) { + /* toggle reset lines */ + gpio_direction_output(tsin->rst_gpio, 0); + usleep_range(3500, 5000); + gpio_direction_output(tsin->rst_gpio, 1); + usleep_range(3000, 5000); + } + + tsin->demux_mapping = index; + + dev_dbg(fei->dev, + "channel=%p n=%d tsin_num=%d, invert-ts-clk=%d\n\t" + "serial-not-parallel=%d pkt-clk-valid=%d dvb-card=%d\n", + fei->channel_data[index], index, + tsin->tsin_id, tsin->invert_ts_clk, + tsin->serial_not_parallel, tsin->async_not_sync, + tsin->dvb_card); + + index++; + } + + /* Setup timer interrupt */ + init_timer(&fei->timer); + fei->timer.function = c8sectpfe_timer_interrupt; + fei->timer.data = (unsigned long)fei; + + mutex_init(&fei->lock); + + /* Get the configuration information about the tuners */ + ret = c8sectpfe_tuner_register_frontend(&fei->c8sectpfe[0], + (void *)fei, + c8sectpfe_start_feed, + c8sectpfe_stop_feed); + if (ret) { + dev_err(dev, "c8sectpfe_tuner_register_frontend failed (%d)\n", + ret); + goto err_clk_disable; + } + + /* ensure all other init has been done before requesting firmware */ + ret = load_c8sectpfe_fw_step1(fei); + if (ret) { + dev_err(dev, "Couldn't load slim core firmware\n"); + goto err_clk_disable; + } + + c8sectpfe_debugfs_init(fei); + + return 0; + +err_clk_disable: + /* TODO uncomment when upstream has taken a reference on this clk */ + /*clk_disable_unprepare(fei->c8sectpfeclk);*/ + return ret; +} + +static int c8sectpfe_remove(struct platform_device *pdev) +{ + struct c8sectpfei *fei = platform_get_drvdata(pdev); + struct channel_info *channel; + int i; + + wait_for_completion(&fei->fw_ack); + + c8sectpfe_tuner_unregister_frontend(fei->c8sectpfe[0], fei); + + /* + * Now loop through and un-configure each of the InputBlock resources + */ + for (i = 0; i < fei->tsin_count; i++) { + channel = fei->channel_data[i]; + free_input_block(fei, channel); + } + + c8sectpfe_debugfs_exit(fei); + + dev_info(fei->dev, "Stopping memdma SLIM core\n"); + if (readl(fei->io + DMA_CPU_RUN)) + writel(0x0, fei->io + DMA_CPU_RUN); + + /* unclock all internal IP's */ + if (readl(fei->io + SYS_INPUT_CLKEN)) + writel(0, fei->io + SYS_INPUT_CLKEN); + + if (readl(fei->io + SYS_OTHER_CLKEN)) + writel(0, fei->io + SYS_OTHER_CLKEN); + + /* TODO uncomment when upstream has taken a reference on this clk */ + /* + if (fei->c8sectpfeclk) + clk_disable_unprepare(fei->c8sectpfeclk); + */ + + return 0; +} + + +static int configure_channels(struct c8sectpfei *fei) +{ + int index = 0, ret; + struct channel_info *tsin; + struct device_node *child, *np = fei->dev->of_node; + + /* iterate round each tsin and configure memdma descriptor and IB hw */ + for_each_child_of_node(np, child) { + + tsin = fei->channel_data[index]; + + ret = configure_memdma_and_inputblock(fei, + fei->channel_data[index]); + + if (ret) { + dev_err(fei->dev, + "configure_memdma_and_inputblock failed\n"); + goto err_unmap; + } + index++; + } + + return 0; + +err_unmap: + for (index = 0; index < fei->tsin_count; index++) { + tsin = fei->channel_data[index]; + free_input_block(fei, tsin); + } + return ret; +} + +static int +c8sectpfe_elf_sanity_check(struct c8sectpfei *fei, const struct firmware *fw) +{ + struct elf32_hdr *ehdr; + char class; + + if (!fw) { + dev_err(fei->dev, "failed to load %s\n", FIRMWARE_MEMDMA); + return -EINVAL; + } + + if (fw->size < sizeof(struct elf32_hdr)) { + dev_err(fei->dev, "Image is too small\n"); + return -EINVAL; + } + + ehdr = (struct elf32_hdr *)fw->data; + + /* We only support ELF32 at this point */ + class = ehdr->e_ident[EI_CLASS]; + if (class != ELFCLASS32) { + dev_err(fei->dev, "Unsupported class: %d\n", class); + return -EINVAL; + } + + if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) { + dev_err(fei->dev, "Unsupported firmware endianness\n"); + return -EINVAL; + } + + if (fw->size < ehdr->e_shoff + sizeof(struct elf32_shdr)) { + dev_err(fei->dev, "Image is too small\n"); + return -EINVAL; + } + + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + dev_err(fei->dev, "Image is corrupted (bad magic)\n"); + return -EINVAL; + } + + /* Check ELF magic */ + ehdr = (Elf32_Ehdr *)fw->data; + if (ehdr->e_ident[EI_MAG0] != ELFMAG0 || + ehdr->e_ident[EI_MAG1] != ELFMAG1 || + ehdr->e_ident[EI_MAG2] != ELFMAG2 || + ehdr->e_ident[EI_MAG3] != ELFMAG3) { + dev_err(fei->dev, "Invalid ELF magic\n"); + return -EINVAL; + } + + if (ehdr->e_type != ET_EXEC) { + dev_err(fei->dev, "Unsupported ELF header type\n"); + return -EINVAL; + } + + if (ehdr->e_phoff > fw->size) { + dev_err(fei->dev, "Firmware size is too small\n"); + return -EINVAL; + } + + return 0; +} + + +static void load_imem_segment(struct c8sectpfei *fei, Elf32_Phdr *phdr, + const struct firmware *fw, u8 __iomem *dest, + int seg_num) +{ + const u8 *imem_src = fw->data + phdr->p_offset; + int i; + + /* + * For IMEM segments, the segment contains 24-bit + * instructions which must be padded to 32-bit + * instructions before being written. The written + * segment is padded with NOP instructions. + */ + + dev_dbg(fei->dev, + "Loading IMEM segment %d 0x%08x\n\t" + " (0x%x bytes) -> 0x%p (0x%x bytes)\n", seg_num, + phdr->p_paddr, phdr->p_filesz, + dest, phdr->p_memsz + phdr->p_memsz / 3); + + for (i = 0; i < phdr->p_filesz; i++) { + + writeb(readb((void __iomem *)imem_src), (void __iomem *)dest); + + /* Every 3 bytes, add an additional + * padding zero in destination */ + if (i % 3 == 2) { + dest++; + writeb(0x00, (void __iomem *)dest); + } + + dest++; + imem_src++; + } +} + +static void load_dmem_segment(struct c8sectpfei *fei, Elf32_Phdr *phdr, + const struct firmware *fw, u8 __iomem *dst, int seg_num) +{ + /* + * For DMEM segments copy the segment data from the ELF + * file and pad segment with zeroes + */ + + dev_dbg(fei->dev, + "Loading DMEM segment %d 0x%08x\n\t" + "(0x%x bytes) -> 0x%p (0x%x bytes)\n", + seg_num, phdr->p_paddr, phdr->p_filesz, + dst, phdr->p_memsz); + + memcpy((void __iomem *)dst, (void *)fw->data + phdr->p_offset, + phdr->p_filesz); + + memset((void __iomem *)dst + phdr->p_filesz, 0, + phdr->p_memsz - phdr->p_filesz); +} + +static int load_slim_core_fw(const struct firmware *fw, void *context) +{ + struct c8sectpfei *fei = context; + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdr; + u8 __iomem *dst; + int err = 0, i; + + if (!fw || !context) + return -EINVAL; + + ehdr = (Elf32_Ehdr *)fw->data; + phdr = (Elf32_Phdr *)(fw->data + ehdr->e_phoff); + + /* go through the available ELF segments */ + for (i = 0; i < ehdr->e_phnum; i++, phdr++) { + + /* Only consider LOAD segments */ + if (phdr->p_type != PT_LOAD) + continue; + + /* + * Check segment is contained within the fw->data buffer + */ + if (phdr->p_offset + phdr->p_filesz > fw->size) { + dev_err(fei->dev, + "Segment %d is outside of firmware file\n", i); + err = -EINVAL; + break; + } + + /* + * MEMDMA IMEM has executable flag set, otherwise load + * this segment into DMEM. + * + */ + + if (phdr->p_flags & PF_X) { + dst = (u8 __iomem *) fei->io + DMA_MEMDMA_IMEM; + /* + * The Slim ELF file uses 32-bit word addressing for + * load offsets. + */ + dst += (phdr->p_paddr & 0xFFFFF) * sizeof(unsigned int); + load_imem_segment(fei, phdr, fw, dst, i); + } else { + dst = (u8 __iomem *) fei->io + DMA_MEMDMA_DMEM; + /* + * The Slim ELF file uses 32-bit word addressing for + * load offsets. + */ + dst += (phdr->p_paddr & 0xFFFFF) * sizeof(unsigned int); + load_dmem_segment(fei, phdr, fw, dst, i); + } + } + + release_firmware(fw); + return err; +} + +static void load_c8sectpfe_fw_cb(const struct firmware *fw, void *context) +{ + struct c8sectpfei *fei = context; + int err; + + err = c8sectpfe_elf_sanity_check(fei, fw); + if (err) { + dev_err(fei->dev, "c8sectpfe_elf_sanity_check failed err=(%d)\n" + , err); + goto err; + } + + err = load_slim_core_fw(fw, context); + if (err) { + dev_err(fei->dev, "load_slim_core_fw failed err=(%d)\n", err); + goto err; + } + + /* now the firmware is loaded configure the input blocks */ + err = configure_channels(fei); + if (err) { + dev_err(fei->dev, "configure_channels failed err=(%d)\n", err); + goto err; + } + + /* + * STBus target port can access IMEM and DMEM ports + * without waiting for CPU + */ + writel(0x1, fei->io + DMA_PER_STBUS_SYNC); + + dev_info(fei->dev, "Boot the memdma SLIM core\n"); + writel(0x1, fei->io + DMA_CPU_RUN); + + atomic_set(&fei->fw_loaded, 1); +err: + complete_all(&fei->fw_ack); +} + +static int load_c8sectpfe_fw_step1(struct c8sectpfei *fei) +{ + int err; + + dev_info(fei->dev, "Loading firmware: %s\n", FIRMWARE_MEMDMA); + + init_completion(&fei->fw_ack); + atomic_set(&fei->fw_loaded, 0); + + err = reject_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + FIRMWARE_MEMDMA, fei->dev, GFP_KERNEL, fei, + load_c8sectpfe_fw_cb); + + if (err) { + dev_err(fei->dev, "request_firmware_nowait err: %d.\n", err); + complete_all(&fei->fw_ack); + return err; + } + + return 0; +} + +static const struct of_device_id c8sectpfe_match[] = { + { .compatible = "st,stih407-c8sectpfe" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, c8sectpfe_match); + +static struct platform_driver c8sectpfe_driver = { + .driver = { + .name = "c8sectpfe", + .of_match_table = of_match_ptr(c8sectpfe_match), + }, + .probe = c8sectpfe_probe, + .remove = c8sectpfe_remove, +}; + +module_platform_driver(c8sectpfe_driver); + +MODULE_AUTHOR("Peter Bennett <peter.bennett@st.com>"); +MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>"); +MODULE_DESCRIPTION("C8SECTPFE STi DVB Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-core.h b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-core.h new file mode 100644 index 000000000..39e7a221a --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-core.h @@ -0,0 +1,288 @@ +/* + * c8sectpfe-core.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett <peter.bennett@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#ifndef _C8SECTPFE_CORE_H_ +#define _C8SECTPFE_CORE_H_ + +#define C8SECTPFEI_MAXCHANNEL 16 +#define C8SECTPFEI_MAXADAPTER 3 + +#define C8SECTPFE_MAX_TSIN_CHAN 8 + +struct channel_info { + + int tsin_id; + bool invert_ts_clk; + bool serial_not_parallel; + bool async_not_sync; + int i2c; + int dvb_card; + + int rst_gpio; + + struct i2c_adapter *i2c_adapter; + struct i2c_adapter *tuner_i2c; + struct i2c_adapter *lnb_i2c; + struct i2c_client *i2c_client; + struct dvb_frontend *frontend; + + struct pinctrl_state *pstate; + + int demux_mapping; + int active; + + void *back_buffer_start; + void *back_buffer_aligned; + dma_addr_t back_buffer_busaddr; + + void *pid_buffer_start; + void *pid_buffer_aligned; + dma_addr_t pid_buffer_busaddr; + + unsigned long fifo; + + struct completion idle_completion; + struct tasklet_struct tsklet; + + struct c8sectpfei *fei; + void __iomem *irec; + +}; + +struct c8sectpfe_hw { + int num_ib; + int num_mib; + int num_swts; + int num_tsout; + int num_ccsc; + int num_ram; + int num_tp; +}; + +struct c8sectpfei { + + struct device *dev; + struct pinctrl *pinctrl; + + struct dentry *root; + struct debugfs_regset32 *regset; + struct completion fw_ack; + atomic_t fw_loaded; + + int tsin_count; + + struct c8sectpfe_hw hw_stats; + + struct c8sectpfe *c8sectpfe[C8SECTPFEI_MAXADAPTER]; + + int mapping[C8SECTPFEI_MAXCHANNEL]; + + struct mutex lock; + + struct timer_list timer; /* timer interrupts for outputs */ + + void __iomem *io; + void __iomem *sram; + + unsigned long sram_size; + + struct channel_info *channel_data[C8SECTPFE_MAX_TSIN_CHAN]; + + struct clk *c8sectpfeclk; + int nima_rst_gpio; + int nimb_rst_gpio; + + int idle_irq; + int error_irq; + + int global_feed_count; +}; + +/* C8SECTPFE SYS Regs list */ + +#define SYS_INPUT_ERR_STATUS 0x0 +#define SYS_OTHER_ERR_STATUS 0x8 +#define SYS_INPUT_ERR_MASK 0x10 +#define SYS_OTHER_ERR_MASK 0x18 +#define SYS_DMA_ROUTE 0x20 +#define SYS_INPUT_CLKEN 0x30 +#define IBENABLE_MASK 0x7F + +#define SYS_OTHER_CLKEN 0x38 +#define TSDMAENABLE BIT(1) +#define MEMDMAENABLE BIT(0) + +#define SYS_CFG_NUM_IB 0x200 +#define SYS_CFG_NUM_MIB 0x204 +#define SYS_CFG_NUM_SWTS 0x208 +#define SYS_CFG_NUM_TSOUT 0x20C +#define SYS_CFG_NUM_CCSC 0x210 +#define SYS_CFG_NUM_RAM 0x214 +#define SYS_CFG_NUM_TP 0x218 + +/* Input Block Regs */ + +#define C8SECTPFE_INPUTBLK_OFFSET 0x1000 +#define C8SECTPFE_CHANNEL_OFFSET(x) ((x*0x40) + C8SECTPFE_INPUTBLK_OFFSET) + +#define C8SECTPFE_IB_IP_FMT_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x00) +#define C8SECTPFE_IGNORE_ERR_AT_SOP BIT(7) +#define C8SECTPFE_IGNORE_ERR_IN_PKT BIT(6) +#define C8SECTPFE_IGNORE_ERR_IN_BYTE BIT(5) +#define C8SECTPFE_INVERT_TSCLK BIT(4) +#define C8SECTPFE_ALIGN_BYTE_SOP BIT(3) +#define C8SECTPFE_ASYNC_NOT_SYNC BIT(2) +#define C8SECTPFE_BYTE_ENDIANNESS_MSB BIT(1) +#define C8SECTPFE_SERIAL_NOT_PARALLEL BIT(0) + +#define C8SECTPFE_IB_SYNCLCKDRP_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x04) +#define C8SECTPFE_SYNC(x) (x & 0xf) +#define C8SECTPFE_DROP(x) ((x<<4) & 0xf) +#define C8SECTPFE_TOKEN(x) ((x<<8) & 0xff00) +#define C8SECTPFE_SLDENDIANNESS BIT(16) + +#define C8SECTPFE_IB_TAGBYTES_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x08) +#define C8SECTPFE_TAG_HEADER(x) (x << 16) +#define C8SECTPFE_TAG_COUNTER(x) ((x<<1) & 0x7fff) +#define C8SECTPFE_TAG_ENABLE BIT(0) + +#define C8SECTPFE_IB_PID_SET(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x0C) +#define C8SECTPFE_PID_OFFSET(x) (x & 0x3f) +#define C8SECTPFE_PID_NUMBITS(x) ((x << 6) & 0xfff) +#define C8SECTPFE_PID_ENABLE BIT(31) + +#define C8SECTPFE_IB_PKT_LEN(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x10) + +#define C8SECTPFE_IB_BUFF_STRT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x14) +#define C8SECTPFE_IB_BUFF_END(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x18) +#define C8SECTPFE_IB_READ_PNT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x1C) +#define C8SECTPFE_IB_WRT_PNT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x20) + +#define C8SECTPFE_IB_PRI_THRLD(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x24) +#define C8SECTPFE_PRI_VALUE(x) (x & 0x7fffff) +#define C8SECTPFE_PRI_LOWPRI(x) ((x & 0xf) << 24) +#define C8SECTPFE_PRI_HIGHPRI(x) ((x & 0xf) << 28) + +#define C8SECTPFE_IB_STAT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x28) +#define C8SECTPFE_STAT_FIFO_OVERFLOW(x) (x & 0x1) +#define C8SECTPFE_STAT_BUFFER_OVERFLOW(x) (x & 0x2) +#define C8SECTPFE_STAT_OUTOFORDERRP(x) (x & 0x4) +#define C8SECTPFE_STAT_PID_OVERFLOW(x) (x & 0x8) +#define C8SECTPFE_STAT_PKT_OVERFLOW(x) (x & 0x10) +#define C8SECTPFE_STAT_ERROR_PACKETS(x) ((x >> 8) & 0xf) +#define C8SECTPFE_STAT_SHORT_PACKETS(x) ((x >> 12) & 0xf) + +#define C8SECTPFE_IB_MASK(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x2C) +#define C8SECTPFE_MASK_FIFO_OVERFLOW BIT(0) +#define C8SECTPFE_MASK_BUFFER_OVERFLOW BIT(1) +#define C8SECTPFE_MASK_OUTOFORDERRP(x) BIT(2) +#define C8SECTPFE_MASK_PID_OVERFLOW(x) BIT(3) +#define C8SECTPFE_MASK_PKT_OVERFLOW(x) BIT(4) +#define C8SECTPFE_MASK_ERROR_PACKETS(x) ((x & 0xf) << 8) +#define C8SECTPFE_MASK_SHORT_PACKETS(x) ((x & 0xf) >> 12) + +#define C8SECTPFE_IB_SYS(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x30) +#define C8SECTPFE_SYS_RESET BIT(1) +#define C8SECTPFE_SYS_ENABLE BIT(0) + +/* + * Ponter record data structure required for each input block + * see Table 82 on page 167 of functional specification. + */ + +#define DMA_PRDS_MEMBASE 0x0 /* Internal sram base address */ +#define DMA_PRDS_MEMTOP 0x4 /* Internal sram top address */ + +/* + * TS packet size, including tag bytes added by input block, + * rounded up to the next multiple of 8 bytes. The packet size, + * including any tagging bytes and rounded up to the nearest + * multiple of 8 bytes must be less than 255 bytes. + */ +#define DMA_PRDS_PKTSIZE 0x8 +#define DMA_PRDS_TPENABLE 0xc + +#define TP0_OFFSET 0x10 +#define DMA_PRDS_BUSBASE_TP(x) ((0x10*x) + TP0_OFFSET) +#define DMA_PRDS_BUSTOP_TP(x) ((0x10*x) + TP0_OFFSET + 0x4) +#define DMA_PRDS_BUSWP_TP(x) ((0x10*x) + TP0_OFFSET + 0x8) +#define DMA_PRDS_BUSRP_TP(x) ((0x10*x) + TP0_OFFSET + 0xc) + +#define DMA_PRDS_SIZE (0x20) + +#define DMA_MEMDMA_OFFSET 0x4000 +#define DMA_IMEM_OFFSET 0x0 +#define DMA_DMEM_OFFSET 0x4000 +#define DMA_CPU 0x8000 +#define DMA_PER_OFFSET 0xb000 + +#define DMA_MEMDMA_DMEM (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET) +#define DMA_MEMDMA_IMEM (DMA_MEMDMA_OFFSET + DMA_IMEM_OFFSET) + +/* XP70 Slim core regs */ +#define DMA_CPU_ID (DMA_MEMDMA_OFFSET + DMA_CPU + 0x0) +#define DMA_CPU_VCR (DMA_MEMDMA_OFFSET + DMA_CPU + 0x4) +#define DMA_CPU_RUN (DMA_MEMDMA_OFFSET + DMA_CPU + 0x8) +#define DMA_CPU_CLOCKGATE (DMA_MEMDMA_OFFSET + DMA_CPU + 0xc) +#define DMA_CPU_PC (DMA_MEMDMA_OFFSET + DMA_CPU + 0x20) + +/* Enable Interrupt for a IB */ +#define DMA_PER_TPn_DREQ_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xd00) +/* Ack interrupt by setting corresponding bit */ +#define DMA_PER_TPn_DACK_SET (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xd80) +#define DMA_PER_TPn_DREQ (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xe00) +#define DMA_PER_TPn_DACK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xe80) +#define DMA_PER_DREQ_MODE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf80) +#define DMA_PER_STBUS_SYNC (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf88) +#define DMA_PER_STBUS_ACCESS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf8c) +#define DMA_PER_STBUS_ADDRESS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf90) +#define DMA_PER_IDLE_INT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfa8) +#define DMA_PER_PRIORITY (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfac) +#define DMA_PER_MAX_OPCODE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfb0) +#define DMA_PER_MAX_CHUNK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfb4) +#define DMA_PER_PAGE_SIZE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfbc) +#define DMA_PER_MBOX_STATUS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfc0) +#define DMA_PER_MBOX_SET (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfc8) +#define DMA_PER_MBOX_CLEAR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfd0) +#define DMA_PER_MBOX_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfd8) +#define DMA_PER_INJECT_PKT_SRC (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe0) +#define DMA_PER_INJECT_PKT_DEST (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe4) +#define DMA_PER_INJECT_PKT_ADDR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe8) +#define DMA_PER_INJECT_PKT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfec) +#define DMA_PER_PAT_PTR_INIT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff0) +#define DMA_PER_PAT_PTR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff4) +#define DMA_PER_SLEEP_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff8) +#define DMA_PER_SLEEP_COUNTER (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xffc) +/* #define DMA_RF_CPUREGn DMA_RFBASEADDR n=0 to 15) slim regsa */ + +/* The following are from DMA_DMEM_BaseAddress */ +#define DMA_FIRMWARE_VERSION (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x0) +#define DMA_PTRREC_BASE (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x4) +#define DMA_PTRREC_INPUT_OFFSET (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x8) +#define DMA_ERRREC_BASE (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0xc) +#define DMA_ERROR_RECORD(n) ((n*4) + DMA_ERRREC_BASE + 0x4) +#define DMA_IDLE_REQ (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x10) +#define IDLEREQ BIT(31) + +#define DMA_FIRMWARE_CONFIG (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x14) + +/* Regs for PID Filter */ + +#define PIDF_OFFSET 0x2800 +#define PIDF_BASE(n) ((n*4) + PIDF_OFFSET) +#define PIDF_LEAK_ENABLE (PIDF_OFFSET + 0x100) +#define PIDF_LEAK_STATUS (PIDF_OFFSET + 0x108) +#define PIDF_LEAK_COUNT_RESET (PIDF_OFFSET + 0x110) +#define PIDF_LEAK_COUNTER (PIDF_OFFSET + 0x114) + +#endif /* _C8SECTPFE_CORE_H_ */ diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-debugfs.c b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-debugfs.c new file mode 100644 index 000000000..e9ba13db4 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-debugfs.c @@ -0,0 +1,271 @@ +/* + * c8sectpfe-debugfs.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include "c8sectpfe-debugfs.h" + +#define dump_register(nm ...) \ +{ \ + .name = #nm, \ + .offset = nm, \ +} + +static const struct debugfs_reg32 fei_sys_regs[] = { + dump_register(SYS_INPUT_ERR_STATUS), + dump_register(SYS_OTHER_ERR_STATUS), + dump_register(SYS_INPUT_ERR_MASK), + dump_register(SYS_DMA_ROUTE), + dump_register(SYS_INPUT_CLKEN), + dump_register(IBENABLE_MASK), + dump_register(SYS_OTHER_CLKEN), + dump_register(SYS_CFG_NUM_IB), + dump_register(SYS_CFG_NUM_MIB), + dump_register(SYS_CFG_NUM_SWTS), + dump_register(SYS_CFG_NUM_TSOUT), + dump_register(SYS_CFG_NUM_CCSC), + dump_register(SYS_CFG_NUM_RAM), + dump_register(SYS_CFG_NUM_TP), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(0)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(0)), + dump_register(C8SECTPFE_IB_PID_SET(0)), + dump_register(C8SECTPFE_IB_PKT_LEN(0)), + dump_register(C8SECTPFE_IB_BUFF_STRT(0)), + dump_register(C8SECTPFE_IB_BUFF_END(0)), + dump_register(C8SECTPFE_IB_READ_PNT(0)), + dump_register(C8SECTPFE_IB_WRT_PNT(0)), + dump_register(C8SECTPFE_IB_PRI_THRLD(0)), + dump_register(C8SECTPFE_IB_STAT(0)), + dump_register(C8SECTPFE_IB_MASK(0)), + dump_register(C8SECTPFE_IB_SYS(0)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(1)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(1)), + dump_register(C8SECTPFE_IB_PID_SET(1)), + dump_register(C8SECTPFE_IB_PKT_LEN(1)), + dump_register(C8SECTPFE_IB_BUFF_STRT(1)), + dump_register(C8SECTPFE_IB_BUFF_END(1)), + dump_register(C8SECTPFE_IB_READ_PNT(1)), + dump_register(C8SECTPFE_IB_WRT_PNT(1)), + dump_register(C8SECTPFE_IB_PRI_THRLD(1)), + dump_register(C8SECTPFE_IB_STAT(1)), + dump_register(C8SECTPFE_IB_MASK(1)), + dump_register(C8SECTPFE_IB_SYS(1)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(2)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(2)), + dump_register(C8SECTPFE_IB_PID_SET(2)), + dump_register(C8SECTPFE_IB_PKT_LEN(2)), + dump_register(C8SECTPFE_IB_BUFF_STRT(2)), + dump_register(C8SECTPFE_IB_BUFF_END(2)), + dump_register(C8SECTPFE_IB_READ_PNT(2)), + dump_register(C8SECTPFE_IB_WRT_PNT(2)), + dump_register(C8SECTPFE_IB_PRI_THRLD(2)), + dump_register(C8SECTPFE_IB_STAT(2)), + dump_register(C8SECTPFE_IB_MASK(2)), + dump_register(C8SECTPFE_IB_SYS(2)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(3)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(3)), + dump_register(C8SECTPFE_IB_PID_SET(3)), + dump_register(C8SECTPFE_IB_PKT_LEN(3)), + dump_register(C8SECTPFE_IB_BUFF_STRT(3)), + dump_register(C8SECTPFE_IB_BUFF_END(3)), + dump_register(C8SECTPFE_IB_READ_PNT(3)), + dump_register(C8SECTPFE_IB_WRT_PNT(3)), + dump_register(C8SECTPFE_IB_PRI_THRLD(3)), + dump_register(C8SECTPFE_IB_STAT(3)), + dump_register(C8SECTPFE_IB_MASK(3)), + dump_register(C8SECTPFE_IB_SYS(3)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(4)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(4)), + dump_register(C8SECTPFE_IB_PID_SET(4)), + dump_register(C8SECTPFE_IB_PKT_LEN(4)), + dump_register(C8SECTPFE_IB_BUFF_STRT(4)), + dump_register(C8SECTPFE_IB_BUFF_END(4)), + dump_register(C8SECTPFE_IB_READ_PNT(4)), + dump_register(C8SECTPFE_IB_WRT_PNT(4)), + dump_register(C8SECTPFE_IB_PRI_THRLD(4)), + dump_register(C8SECTPFE_IB_STAT(4)), + dump_register(C8SECTPFE_IB_MASK(4)), + dump_register(C8SECTPFE_IB_SYS(4)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(5)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(5)), + dump_register(C8SECTPFE_IB_PID_SET(5)), + dump_register(C8SECTPFE_IB_PKT_LEN(5)), + dump_register(C8SECTPFE_IB_BUFF_STRT(5)), + dump_register(C8SECTPFE_IB_BUFF_END(5)), + dump_register(C8SECTPFE_IB_READ_PNT(5)), + dump_register(C8SECTPFE_IB_WRT_PNT(5)), + dump_register(C8SECTPFE_IB_PRI_THRLD(5)), + dump_register(C8SECTPFE_IB_STAT(5)), + dump_register(C8SECTPFE_IB_MASK(5)), + dump_register(C8SECTPFE_IB_SYS(5)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(6)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(6)), + dump_register(C8SECTPFE_IB_PID_SET(6)), + dump_register(C8SECTPFE_IB_PKT_LEN(6)), + dump_register(C8SECTPFE_IB_BUFF_STRT(6)), + dump_register(C8SECTPFE_IB_BUFF_END(6)), + dump_register(C8SECTPFE_IB_READ_PNT(6)), + dump_register(C8SECTPFE_IB_WRT_PNT(6)), + dump_register(C8SECTPFE_IB_PRI_THRLD(6)), + dump_register(C8SECTPFE_IB_STAT(6)), + dump_register(C8SECTPFE_IB_MASK(6)), + dump_register(C8SECTPFE_IB_SYS(6)), + + dump_register(DMA_CPU_ID), + dump_register(DMA_CPU_VCR), + dump_register(DMA_CPU_RUN), + dump_register(DMA_CPU_PC), + + dump_register(DMA_PER_TPn_DREQ_MASK), + dump_register(DMA_PER_TPn_DACK_SET), + dump_register(DMA_PER_TPn_DREQ), + dump_register(DMA_PER_TPn_DACK), + dump_register(DMA_PER_DREQ_MODE), + dump_register(DMA_PER_STBUS_SYNC), + dump_register(DMA_PER_STBUS_ACCESS), + dump_register(DMA_PER_STBUS_ADDRESS), + dump_register(DMA_PER_IDLE_INT), + dump_register(DMA_PER_PRIORITY), + dump_register(DMA_PER_MAX_OPCODE), + dump_register(DMA_PER_MAX_CHUNK), + dump_register(DMA_PER_PAGE_SIZE), + dump_register(DMA_PER_MBOX_STATUS), + dump_register(DMA_PER_MBOX_SET), + dump_register(DMA_PER_MBOX_CLEAR), + dump_register(DMA_PER_MBOX_MASK), + dump_register(DMA_PER_INJECT_PKT_SRC), + dump_register(DMA_PER_INJECT_PKT_DEST), + dump_register(DMA_PER_INJECT_PKT_ADDR), + dump_register(DMA_PER_INJECT_PKT), + dump_register(DMA_PER_PAT_PTR_INIT), + dump_register(DMA_PER_PAT_PTR), + dump_register(DMA_PER_SLEEP_MASK), + dump_register(DMA_PER_SLEEP_COUNTER), + + dump_register(DMA_FIRMWARE_VERSION), + dump_register(DMA_PTRREC_BASE), + dump_register(DMA_PTRREC_INPUT_OFFSET), + dump_register(DMA_ERRREC_BASE), + + dump_register(DMA_ERROR_RECORD(0)), + dump_register(DMA_ERROR_RECORD(1)), + dump_register(DMA_ERROR_RECORD(2)), + dump_register(DMA_ERROR_RECORD(3)), + dump_register(DMA_ERROR_RECORD(4)), + dump_register(DMA_ERROR_RECORD(5)), + dump_register(DMA_ERROR_RECORD(6)), + dump_register(DMA_ERROR_RECORD(7)), + dump_register(DMA_ERROR_RECORD(8)), + dump_register(DMA_ERROR_RECORD(9)), + dump_register(DMA_ERROR_RECORD(10)), + dump_register(DMA_ERROR_RECORD(11)), + dump_register(DMA_ERROR_RECORD(12)), + dump_register(DMA_ERROR_RECORD(13)), + dump_register(DMA_ERROR_RECORD(14)), + dump_register(DMA_ERROR_RECORD(15)), + dump_register(DMA_ERROR_RECORD(16)), + dump_register(DMA_ERROR_RECORD(17)), + dump_register(DMA_ERROR_RECORD(18)), + dump_register(DMA_ERROR_RECORD(19)), + dump_register(DMA_ERROR_RECORD(20)), + dump_register(DMA_ERROR_RECORD(21)), + dump_register(DMA_ERROR_RECORD(22)), + + dump_register(DMA_IDLE_REQ), + dump_register(DMA_FIRMWARE_CONFIG), + + dump_register(PIDF_BASE(0)), + dump_register(PIDF_BASE(1)), + dump_register(PIDF_BASE(2)), + dump_register(PIDF_BASE(3)), + dump_register(PIDF_BASE(4)), + dump_register(PIDF_BASE(5)), + dump_register(PIDF_BASE(6)), + dump_register(PIDF_BASE(7)), + dump_register(PIDF_BASE(8)), + dump_register(PIDF_BASE(9)), + dump_register(PIDF_BASE(10)), + dump_register(PIDF_BASE(11)), + dump_register(PIDF_BASE(12)), + dump_register(PIDF_BASE(13)), + dump_register(PIDF_BASE(14)), + dump_register(PIDF_BASE(15)), + dump_register(PIDF_BASE(16)), + dump_register(PIDF_BASE(17)), + dump_register(PIDF_BASE(18)), + dump_register(PIDF_BASE(19)), + dump_register(PIDF_BASE(20)), + dump_register(PIDF_BASE(21)), + dump_register(PIDF_BASE(22)), + dump_register(PIDF_LEAK_ENABLE), + dump_register(PIDF_LEAK_STATUS), + dump_register(PIDF_LEAK_COUNT_RESET), + dump_register(PIDF_LEAK_COUNTER), +}; + +void c8sectpfe_debugfs_init(struct c8sectpfei *fei) +{ + struct dentry *root; + struct dentry *file; + + root = debugfs_create_dir("c8sectpfe", NULL); + if (!root) + goto err; + + fei->root = root; + + fei->regset = devm_kzalloc(fei->dev, sizeof(*fei->regset), GFP_KERNEL); + if (!fei->regset) + goto err; + + fei->regset->regs = fei_sys_regs; + fei->regset->nregs = ARRAY_SIZE(fei_sys_regs); + fei->regset->base = fei->io; + + file = debugfs_create_regset32("registers", S_IRUGO, root, + fei->regset); + if (!file) { + dev_err(fei->dev, + "%s not able to create 'registers' debugfs\n" + , __func__); + goto err; + } + + return; + +err: + debugfs_remove_recursive(root); +} + +void c8sectpfe_debugfs_exit(struct c8sectpfei *fei) +{ + debugfs_remove_recursive(fei->root); + fei->root = NULL; +} diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-debugfs.h b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-debugfs.h new file mode 100644 index 000000000..8af1ac137 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-debugfs.h @@ -0,0 +1,26 @@ +/** + * c8sectpfe-debugfs.h - C8SECTPFE STi DVB driver debugfs header + * + * Copyright (c) STMicroelectronics 2015 + * + * Authors: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __C8SECTPFE_DEBUG_H +#define __C8SECTPFE_DEBUG_H + +#include "c8sectpfe-core.h" + +void c8sectpfe_debugfs_init(struct c8sectpfei *); +void c8sectpfe_debugfs_exit(struct c8sectpfei *); + +#endif /* __C8SECTPFE_DEBUG_H */ diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-dvb.c b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-dvb.c new file mode 100644 index 000000000..69d7fe447 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-dvb.c @@ -0,0 +1,244 @@ +/* + * c8sectpfe-dvb.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/version.h> + +#include <dt-bindings/media/c8sectpfe.h> + +#include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" +#include "c8sectpfe-dvb.h" + +#include "dvb-pll.h" +#include "lnbh24.h" +#include "stv0367.h" +#include "stv0367_priv.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "tda18212.h" + +static inline const char *dvb_card_str(unsigned int c) +{ + switch (c) { + case STV0367_TDA18212_NIMA_1: return "STV0367_TDA18212_NIMA_1"; + case STV0367_TDA18212_NIMA_2: return "STV0367_TDA18212_NIMA_2"; + case STV0367_TDA18212_NIMB_1: return "STV0367_TDA18212_NIMB_1"; + case STV0367_TDA18212_NIMB_2: return "STV0367_TDA18212_NIMB_2"; + case STV0903_6110_LNB24_NIMA: return "STV0903_6110_LNB24_NIMA"; + case STV0903_6110_LNB24_NIMB: return "STV0903_6110_LNB24_NIMB"; + default: return "unknown dvb frontend card"; + } +} + +static struct stv090x_config stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + .xtal = 16000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + + .repeater_level = STV090x_RPTLEVEL_64, + + .tuner_init = NULL, + .tuner_set_mode = NULL, + .tuner_set_frequency = NULL, + .tuner_get_frequency = NULL, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = NULL, + .tuner_set_bbgain = NULL, + .tuner_get_bbgain = NULL, + .tuner_set_refclk = NULL, + .tuner_get_status = NULL, +}; + +static struct stv6110x_config stv6110x_config = { + .addr = 0x60, + .refclk = 16000000, +}; + +#define NIMA 0 +#define NIMB 1 + +static struct stv0367_config stv0367_tda18212_config[] = { + { + .demod_address = 0x1c, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1d, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1e, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, +}; + +static struct tda18212_config tda18212_conf = { + .if_dvbt_6 = 4150, + .if_dvbt_7 = 4150, + .if_dvbt_8 = 4500, + .if_dvbc = 5000, +}; + +int c8sectpfe_frontend_attach(struct dvb_frontend **fe, + struct c8sectpfe *c8sectpfe, + struct channel_info *tsin, int chan_num) +{ + struct tda18212_config *tda18212; + struct stv6110x_devctl *fe2; + struct i2c_client *client; + struct i2c_board_info tda18212_info = { + .type = "tda18212", + .addr = 0x60, + }; + + if (!tsin) + return -EINVAL; + + switch (tsin->dvb_card) { + + case STV0367_TDA18212_NIMA_1: + case STV0367_TDA18212_NIMA_2: + case STV0367_TDA18212_NIMB_1: + case STV0367_TDA18212_NIMB_2: + if (tsin->dvb_card == STV0367_TDA18212_NIMA_1) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[0], + tsin->i2c_adapter); + else if (tsin->dvb_card == STV0367_TDA18212_NIMB_1) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[1], + tsin->i2c_adapter); + else + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[2], + tsin->i2c_adapter); + + if (!*fe) { + dev_err(c8sectpfe->device, + "%s: stv0367ter_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + }; + + /* + * init the demod so that i2c gate_ctrl + * to the tuner works correctly + */ + (*fe)->ops.init(*fe); + + /* Allocate the tda18212 structure */ + tda18212 = devm_kzalloc(c8sectpfe->device, + sizeof(struct tda18212_config), + GFP_KERNEL); + if (!tda18212) { + dev_err(c8sectpfe->device, + "%s: devm_kzalloc failed\n", __func__); + return -ENOMEM; + } + + memcpy(tda18212, &tda18212_conf, + sizeof(struct tda18212_config)); + + tda18212->fe = (*fe); + + tda18212_info.platform_data = tda18212; + + /* attach tuner */ + request_module("tda18212"); + client = i2c_new_device(tsin->i2c_adapter, &tda18212_info); + if (!client || !client->dev.driver) { + dvb_frontend_detach(*fe); + return -ENODEV; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(*fe); + return -ENODEV; + } + + tsin->i2c_client = client; + + break; + + case STV0903_6110_LNB24_NIMA: + *fe = dvb_attach(stv090x_attach, &stv090x_config, + tsin->i2c_adapter, STV090x_DEMODULATOR_0); + if (!*fe) { + dev_err(c8sectpfe->device, "%s: stv090x_attach failed\n" + "\tfor NIM card %s\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + fe2 = dvb_attach(stv6110x_attach, *fe, + &stv6110x_config, tsin->i2c_adapter); + if (!fe2) { + dev_err(c8sectpfe->device, + "%s: stv6110x_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + }; + + stv090x_config.tuner_init = fe2->tuner_init; + stv090x_config.tuner_set_mode = fe2->tuner_set_mode; + stv090x_config.tuner_set_frequency = fe2->tuner_set_frequency; + stv090x_config.tuner_get_frequency = fe2->tuner_get_frequency; + stv090x_config.tuner_set_bandwidth = fe2->tuner_set_bandwidth; + stv090x_config.tuner_get_bandwidth = fe2->tuner_get_bandwidth; + stv090x_config.tuner_set_bbgain = fe2->tuner_set_bbgain; + stv090x_config.tuner_get_bbgain = fe2->tuner_get_bbgain; + stv090x_config.tuner_set_refclk = fe2->tuner_set_refclk; + stv090x_config.tuner_get_status = fe2->tuner_get_status; + + dvb_attach(lnbh24_attach, *fe, tsin->i2c_adapter, 0, 0, 0x9); + break; + + default: + dev_err(c8sectpfe->device, + "%s: DVB frontend card %s not yet supported\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + (*fe)->id = chan_num; + + dev_info(c8sectpfe->device, + "DVB frontend card %s successfully attached", + dvb_card_str(tsin->dvb_card)); + return 0; +} diff --git a/drivers/media/platform/sti/c8sectpfe/c8sectpfe-dvb.h b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-dvb.h new file mode 100644 index 000000000..bd366dbc8 --- /dev/null +++ b/drivers/media/platform/sti/c8sectpfe/c8sectpfe-dvb.h @@ -0,0 +1,20 @@ +/* + * c8sectpfe-common.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#ifndef _C8SECTPFE_DVB_H_ +#define _C8SECTPFE_DVB_H_ + +int c8sectpfe_frontend_attach(struct dvb_frontend **fe, + struct c8sectpfe *c8sectpfe, struct channel_info *tsin, + int chan_num); + +#endif diff --git a/drivers/media/platform/vivid/vivid-vid-cap.c b/drivers/media/platform/vivid/vivid-vid-cap.c index c4268d1b2..ed0b8788a 100644 --- a/drivers/media/platform/vivid/vivid-vid-cap.c +++ b/drivers/media/platform/vivid/vivid-vid-cap.c @@ -1627,7 +1627,7 @@ static bool valid_cvt_gtf_timings(struct v4l2_dv_timings *timings) h_freq = (u32)bt->pixelclock / total_h_pixel; if (bt->standards == 0 || (bt->standards & V4L2_DV_BT_STD_CVT)) { - if (v4l2_detect_cvt(total_v_lines, h_freq, bt->vsync, + if (v4l2_detect_cvt(total_v_lines, h_freq, bt->vsync, bt->width, bt->polarities, bt->interlaced, timings)) return true; } diff --git a/drivers/media/platform/vivid/vivid-vid-out.c b/drivers/media/platform/vivid/vivid-vid-out.c index 0862c1f24..c404e275e 100644 --- a/drivers/media/platform/vivid/vivid-vid-out.c +++ b/drivers/media/platform/vivid/vivid-vid-out.c @@ -1124,15 +1124,26 @@ int vivid_vid_out_s_std(struct file *file, void *priv, v4l2_std_id id) return 0; } +static bool valid_cvt_gtf_timings(struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + + if ((bt->standards & (V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF)) && + v4l2_valid_dv_timings(timings, &vivid_dv_timings_cap, NULL, NULL)) + return true; + + return false; +} + int vivid_vid_out_s_dv_timings(struct file *file, void *_fh, struct v4l2_dv_timings *timings) { struct vivid_dev *dev = video_drvdata(file); - if (!vivid_is_hdmi_out(dev)) return -ENODATA; if (!v4l2_find_dv_timings_cap(timings, &vivid_dv_timings_cap, - 0, NULL, NULL)) + 0, NULL, NULL) && + !valid_cvt_gtf_timings(timings)) return -EINVAL; if (v4l2_match_dv_timings(timings, &dev->dv_timings_out, 0)) return 0; diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c index 913485a90..4e6188638 100644 --- a/drivers/media/platform/vsp1/vsp1_drv.c +++ b/drivers/media/platform/vsp1/vsp1_drv.c @@ -1,7 +1,7 @@ /* * vsp1_drv.c -- R-Car VSP1 Driver * - * Copyright (C) 2013-2014 Renesas Electronics Corporation + * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) * @@ -403,7 +403,10 @@ static int vsp1_pm_suspend(struct device *dev) if (vsp1->ref_count == 0) return 0; + vsp1_pipelines_suspend(vsp1); + clk_disable_unprepare(vsp1->clock); + return 0; } @@ -413,10 +416,14 @@ static int vsp1_pm_resume(struct device *dev) WARN_ON(mutex_is_locked(&vsp1->lock)); - if (vsp1->ref_count) + if (vsp1->ref_count == 0) return 0; - return clk_prepare_enable(vsp1->clock); + clk_prepare_enable(vsp1->clock); + + vsp1_pipelines_resume(vsp1); + + return 0; } #endif diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c index a453bb4dd..fd95a75b0 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.c +++ b/drivers/media/platform/vsp1/vsp1_entity.c @@ -24,22 +24,24 @@ bool vsp1_entity_is_streaming(struct vsp1_entity *entity) { + unsigned long flags; bool streaming; - mutex_lock(&entity->lock); + spin_lock_irqsave(&entity->lock, flags); streaming = entity->streaming; - mutex_unlock(&entity->lock); + spin_unlock_irqrestore(&entity->lock, flags); return streaming; } int vsp1_entity_set_streaming(struct vsp1_entity *entity, bool streaming) { + unsigned long flags; int ret; - mutex_lock(&entity->lock); + spin_lock_irqsave(&entity->lock, flags); entity->streaming = streaming; - mutex_unlock(&entity->lock); + spin_unlock_irqrestore(&entity->lock, flags); if (!streaming) return 0; @@ -49,9 +51,9 @@ int vsp1_entity_set_streaming(struct vsp1_entity *entity, bool streaming) ret = v4l2_ctrl_handler_setup(entity->subdev.ctrl_handler); if (ret < 0) { - mutex_lock(&entity->lock); + spin_lock_irqsave(&entity->lock, flags); entity->streaming = false; - mutex_unlock(&entity->lock); + spin_unlock_irqrestore(&entity->lock, flags); } return ret; @@ -193,7 +195,7 @@ int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, if (i == ARRAY_SIZE(vsp1_routes)) return -EINVAL; - mutex_init(&entity->lock); + spin_lock_init(&entity->lock); entity->vsp1 = vsp1; entity->source_pad = num_pads - 1; @@ -228,6 +230,4 @@ void vsp1_entity_destroy(struct vsp1_entity *entity) if (entity->subdev.ctrl_handler) v4l2_ctrl_handler_free(entity->subdev.ctrl_handler); media_entity_cleanup(&entity->subdev.entity); - - mutex_destroy(&entity->lock); } diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h index 62c768d1c..8867a5787 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.h +++ b/drivers/media/platform/vsp1/vsp1_entity.h @@ -14,7 +14,7 @@ #define __VSP1_ENTITY_H__ #include <linux/list.h> -#include <linux/mutex.h> +#include <linux/spinlock.h> #include <media/v4l2-subdev.h> @@ -73,7 +73,7 @@ struct vsp1_entity { struct vsp1_video *video; - struct mutex lock; /* Protects the streaming field */ + spinlock_t lock; /* Protects the streaming field */ bool streaming; }; diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h index da3c573e1..25b48738b 100644 --- a/drivers/media/platform/vsp1/vsp1_regs.h +++ b/drivers/media/platform/vsp1/vsp1_regs.h @@ -238,7 +238,7 @@ #define VI6_WPF_SZCLIP_EN (1 << 28) #define VI6_WPF_SZCLIP_OFST_MASK (0xff << 16) #define VI6_WPF_SZCLIP_OFST_SHIFT 16 -#define VI6_WPF_SZCLIP_SIZE_MASK (0x1fff << 0) +#define VI6_WPF_SZCLIP_SIZE_MASK (0xfff << 0) #define VI6_WPF_SZCLIP_SIZE_SHIFT 0 #define VI6_WPF_OUTFMT 0x100c @@ -304,9 +304,9 @@ #define VI6_DPR_HST_ROUTE 0x2044 #define VI6_DPR_HSI_ROUTE 0x2048 #define VI6_DPR_BRU_ROUTE 0x204c -#define VI6_DPR_ROUTE_FXA_MASK (0xff << 8) +#define VI6_DPR_ROUTE_FXA_MASK (0xff << 16) #define VI6_DPR_ROUTE_FXA_SHIFT 16 -#define VI6_DPR_ROUTE_FP_MASK (0xff << 8) +#define VI6_DPR_ROUTE_FP_MASK (0x3f << 8) #define VI6_DPR_ROUTE_FP_SHIFT 8 #define VI6_DPR_ROUTE_RT_MASK (0x3f << 0) #define VI6_DPR_ROUTE_RT_SHIFT 0 diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.c b/drivers/media/platform/vsp1/vsp1_rwpf.c index fa71f4695..9688c219b 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.c +++ b/drivers/media/platform/vsp1/vsp1_rwpf.c @@ -197,6 +197,17 @@ int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, */ format = vsp1_entity_get_pad_format(&rwpf->entity, cfg, RWPF_PAD_SINK, sel->which); + + /* Restrict the crop rectangle coordinates to multiples of 2 to avoid + * shifting the color plane. + */ + if (format->code == MEDIA_BUS_FMT_AYUV8_1X32) { + sel->r.left = ALIGN(sel->r.left, 2); + sel->r.top = ALIGN(sel->r.top, 2); + sel->r.width = round_down(sel->r.width, 2); + sel->r.height = round_down(sel->r.height, 2); + } + sel->r.left = min_t(unsigned int, sel->r.left, format->width - 2); sel->r.top = min_t(unsigned int, sel->r.top, format->height - 2); if (rwpf->entity.type == VSP1_ENTITY_WPF) { diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c index d91f19a9e..3c124c14c 100644 --- a/drivers/media/platform/vsp1/vsp1_video.c +++ b/drivers/media/platform/vsp1/vsp1_video.c @@ -1,7 +1,7 @@ /* * vsp1_video.c -- R-Car VSP1 Video Node * - * Copyright (C) 2013-2014 Renesas Electronics Corporation + * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) * @@ -245,7 +245,7 @@ static int __vsp1_video_try_format(struct vsp1_video *video, * the datasheet, strides not aligned to a multiple of 128 bytes result * in image corruption. */ - for (i = 0; i < max(info->planes, 2U); ++i) { + for (i = 0; i < min(info->planes, 2U); ++i) { unsigned int hsub = i > 0 ? info->hsub : 1; unsigned int vsub = i > 0 ? info->vsub : 1; unsigned int align = 128; @@ -514,6 +514,18 @@ static void vsp1_pipeline_run(struct vsp1_pipeline *pipe) pipe->buffers_ready = 0; } +static bool vsp1_pipeline_stopped(struct vsp1_pipeline *pipe) +{ + unsigned long flags; + bool stopped; + + spin_lock_irqsave(&pipe->irqlock, flags); + stopped = pipe->state == VSP1_PIPELINE_STOPPED, + spin_unlock_irqrestore(&pipe->irqlock, flags); + + return stopped; +} + static int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) { struct vsp1_entity *entity; @@ -525,7 +537,7 @@ static int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) pipe->state = VSP1_PIPELINE_STOPPING; spin_unlock_irqrestore(&pipe->irqlock, flags); - ret = wait_event_timeout(pipe->wq, pipe->state == VSP1_PIPELINE_STOPPED, + ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe), msecs_to_jiffies(500)); ret = ret == 0 ? -ETIMEDOUT : 0; @@ -703,6 +715,73 @@ void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, } } +void vsp1_pipelines_suspend(struct vsp1_device *vsp1) +{ + unsigned long flags; + unsigned int i; + int ret; + + /* To avoid increasing the system suspend time needlessly, loop over the + * pipelines twice, first to set them all to the stopping state, and then + * to wait for the stop to complete. + */ + for (i = 0; i < vsp1->pdata.wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = to_vsp1_pipeline(&wpf->entity.subdev.entity); + if (pipe == NULL) + continue; + + spin_lock_irqsave(&pipe->irqlock, flags); + if (pipe->state == VSP1_PIPELINE_RUNNING) + pipe->state = VSP1_PIPELINE_STOPPING; + spin_unlock_irqrestore(&pipe->irqlock, flags); + } + + for (i = 0; i < vsp1->pdata.wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = to_vsp1_pipeline(&wpf->entity.subdev.entity); + if (pipe == NULL) + continue; + + ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe), + msecs_to_jiffies(500)); + if (ret == 0) + dev_warn(vsp1->dev, "pipeline %u stop timeout\n", + wpf->entity.index); + } +} + +void vsp1_pipelines_resume(struct vsp1_device *vsp1) +{ + unsigned int i; + + /* Resume pipeline all running pipelines. */ + for (i = 0; i < vsp1->pdata.wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = to_vsp1_pipeline(&wpf->entity.subdev.entity); + if (pipe == NULL) + continue; + + if (vsp1_pipeline_ready(pipe)) + vsp1_pipeline_run(pipe); + } +} + /* ----------------------------------------------------------------------------- * videobuf2 Queue Operations */ diff --git a/drivers/media/platform/vsp1/vsp1_video.h b/drivers/media/platform/vsp1/vsp1_video.h index fd2851a82..0887a4d27 100644 --- a/drivers/media/platform/vsp1/vsp1_video.h +++ b/drivers/media/platform/vsp1/vsp1_video.h @@ -1,7 +1,7 @@ /* * vsp1_video.h -- R-Car VSP1 Video Node * - * Copyright (C) 2013-2014 Renesas Electronics Corporation + * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) * @@ -149,4 +149,7 @@ void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, struct vsp1_entity *input, unsigned int alpha); +void vsp1_pipelines_suspend(struct vsp1_device *vsp1); +void vsp1_pipelines_resume(struct vsp1_device *vsp1); + #endif /* __VSP1_VIDEO_H__ */ |