Message ID | 1408969787-23132-1-git-send-email-mikhail.ulyanov@cogentembedded.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
On 08/25/2014 02:29 PM, Mikhail Ulyanov wrote: > This patch contains driver for Renesas R-Car JPEG codec. > > Cnanges since v1: > - s/g_fmt function simplified > - default format for queues added > - dumb vidioc functions added to be in compliance with standard api: > jpu_s_priority, jpu_g_priority Oops, that's a bug elsewhere. Don't add these empty prio ops, this needs to be solved in the v4l2 core. I'll post a patch for this. Regards, Hans > - standard v4l2_ctrl_subscribe_event and v4l2_event_unsubscribe > now in use by the same reason -- To unsubscribe from this list: send the line "unsubscribe linux-sh" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 08/25/2014 02:39 PM, Hans Verkuil wrote: > On 08/25/2014 02:29 PM, Mikhail Ulyanov wrote: >> This patch contains driver for Renesas R-Car JPEG codec. >> >> Cnanges since v1: >> - s/g_fmt function simplified >> - default format for queues added >> - dumb vidioc functions added to be in compliance with standard api: >> jpu_s_priority, jpu_g_priority > > Oops, that's a bug elsewhere. Don't add these empty prio ops, this needs to be > solved in the v4l2 core. > > I'll post a patch for this. After some thought I've decided to allow prio handling for m2m devices. It is actually useful if some application wants exclusive access to the m2m hardware. So I will change v4l2-compliance instead. Regards, Hans -- To unsubscribe from this list: send the line "unsubscribe linux-sh" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hello. On 08/25/2014 04:49 PM, Hans Verkuil wrote: >>> This patch contains driver for Renesas R-Car JPEG codec. >>> Cnanges since v1: >>> - s/g_fmt function simplified >>> - default format for queues added >>> - dumb vidioc functions added to be in compliance with standard api: >>> jpu_s_priority, jpu_g_priority >> Oops, that's a bug elsewhere. Don't add these empty prio ops, this needs to be >> solved in the v4l2 core. >> I'll post a patch for this. > After some thought I've decided to allow prio handling for m2m devices. It is > actually useful if some application wants exclusive access to the m2m hardware. > So I will change v4l2-compliance instead. I take it we don't need to change the driver? Asking because the driver seems stuck for nearly a months now. I'm myself still seeing a place for improvement (register macro naming of the top of my head). Perhaps it's time to take this driver into my own hands... > Regards, > Hans WBR, Sergei -- To unsubscribe from this list: send the line "unsubscribe linux-sh" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hello. On 08/25/2014 04:49 PM, Hans Verkuil wrote: >>> This patch contains driver for Renesas R-Car JPEG codec. >>> Cnanges since v1: >>> - s/g_fmt function simplified >>> - default format for queues added >>> - dumb vidioc functions added to be in compliance with standard api: >>> jpu_s_priority, jpu_g_priority >> Oops, that's a bug elsewhere. Don't add these empty prio ops, this needs to be >> solved in the v4l2 core. >> I'll post a patch for this. > After some thought I've decided to allow prio handling for m2m devices. It is > actually useful if some application wants exclusive access to the m2m hardware. > So I will change v4l2-compliance instead. I take it we don't need to change the driver? Asking because the driver seems stuck for nearly a month now. I'm myself still seeing a place for improvement (register macro naming of the top of my head). Perhaps it's time to take this driver into my own hands... > Regards, > Hans WBR, Sergei -- To unsubscribe from this list: send the line "unsubscribe linux-sh" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Mikhail, > From: linux-media-owner@vger.kernel.org [mailto:linux-media- > owner@vger.kernel.org] On Behalf Of Mikhail Ulyanov > Sent: Monday, August 25, 2014 2:30 PM > > This patch contains driver for Renesas R-Car JPEG codec. > > Cnanges since v1: > - s/g_fmt function simplified > - default format for queues added > - dumb vidioc functions added to be in compliance with standard > api: > jpu_s_priority, jpu_g_priority > - standard v4l2_ctrl_subscribe_event and v4l2_event_unsubscribe > now in use by the same reason The patch looks good to me. However, I would suggest using the BIT macro and making some short functions inline. > Signed-off-by: Mikhail Ulyanov <mikhail.ulyanov@cogentembedded.com> > --- > drivers/media/platform/Kconfig | 11 + > drivers/media/platform/Makefile | 2 + > drivers/media/platform/jpu.c | 1628 > ++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 1641 insertions(+) > create mode 100644 drivers/media/platform/jpu.c > > diff --git a/drivers/media/platform/Kconfig > b/drivers/media/platform/Kconfig index 6d86646..1b8c846 100644 > --- a/drivers/media/platform/Kconfig > +++ b/drivers/media/platform/Kconfig > @@ -220,6 +220,17 @@ config VIDEO_RENESAS_VSP1 > To compile this driver as a module, choose M here: the module > will be called vsp1. > > +config VIDEO_RENESAS_JPU > + tristate "Renesas JPEG Processing Unit" > + depends on VIDEO_DEV && VIDEO_V4L2 > + 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 jpu. > + > config VIDEO_TI_VPE > tristate "TI VPE (Video Processing Engine) driver" > depends on VIDEO_DEV && VIDEO_V4L2 && SOC_DRA7XX diff --git > a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile > index e5269da..e438534 100644 > --- a/drivers/media/platform/Makefile > +++ b/drivers/media/platform/Makefile > @@ -47,6 +47,8 @@ obj-$(CONFIG_SOC_CAMERA) += soc_camera/ > > obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ > > +obj-$(CONFIG_VIDEO_RENESAS_JPU) += jpu.o > + > obj-y += davinci/ > > obj-$(CONFIG_ARCH_OMAP) += omap/ > diff --git a/drivers/media/platform/jpu.c > b/drivers/media/platform/jpu.c new file mode 100644 index > 0000000..da70491 > --- /dev/null > +++ b/drivers/media/platform/jpu.c > @@ -0,0 +1,1628 @@ > +/* > + * Author: Mikhail Ulyanov <source@cogentembedded.com> > + * Copyright (C) 2014 Cogent Embedded, Inc. > + * Copyright (C) 2014 Renesas Electronics Corporation > + * > + * This is based on the drivers/media/platform/s5p-jpu driver by > + * Andrzej Pietrasiewicz and Jacek Anaszewski. > + * > + * 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 <linux/clk.h> > +#include <linux/err.h> > +#include <linux/gfp.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 JPU_M2M_NAME "jpu" > + > +#define JPU_WIDTH_MIN 16 > +#define JPU_HEIGHT_MIN 16 > +#define JPU_WIDTH_MAX 4096 > +#define JPU_HEIGHT_MAX 4096 > +#define JPU_DEFAULT_WIDTH 640 > +#define JPU_DEFAULT_HEIGHT 480 > + > +#define JPU_ENCODE 0 > +#define JPU_DECODE 1 > + > +/* 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) The BIT macro could be used here. > + > +/* > + * JPEG registers and bits > + */ > + > +/* JPEG code mode register */ > +#define JCMOD 0x00 > +#define JCMOD_SOI_DISABLE (1 << 8) > +#define JCMOD_SOI_ENABLE (0 << 8) > +#define JCMOD_PCTR (1 << 7) > +#define JCMOD_MSKIP_DISABLE (0 << 5) > +#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_BRST (1 << 7) > +#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) > + [snip] > = > +===== > + * video ioctl operations > + * > +====================================================================== > = > +===== > + */ > +static void put_byte(unsigned long *p, u8 v) { > + u8 *addr = (u8 *)*p; > + > + *addr = v; > + (*p)++; > +} > + > +static void put_short_be(unsigned long *p, u16 v) { > + u16 *addr = (u16 *)*p; > + > + *addr = cpu_to_be16(v); > + *p += 2; > +} > + > +static void put_word_be(unsigned long *p, u32 v) { > + u32 *addr = (u32 *)*p; > + > + *addr = cpu_to_be32(v); > + *p += 4; > +} The 3 above function could be inline. > + > +static void put_blob_byte(unsigned long *p, const unsigned char *blob, > + unsigned int len) > +{ > + int i; > + > + for (i = 0; i < len; i++) > + put_byte(p, blob[i]); > +} I think this function could also be inline. > + > +static void put_qtbl(unsigned long *p, unsigned char id, > + const unsigned int *qtbl) > +{ > + int i; > + > + put_byte(p, id); > + for (i = 0; i < ARRAY_SIZE(zigzag); i++) > + put_byte(p, *((u8 *)qtbl + zigzag[i])); } > + > +static void put_htbl(unsigned long *p, unsigned char tc, > + const unsigned int *htbl, unsigned int len) { > + int i; > + > + put_byte(p, tc); > + for (i = 0; i < len; i++) > + put_word_be(p, htbl[i]); > +} > + [snip] Best wishes,
Hello. On 9/23/2014 5:31 PM, Kamil Debski wrote: >> From: linux-media-owner@vger.kernel.org [mailto:linux-media- >> owner@vger.kernel.org] On Behalf Of Mikhail Ulyanov >> Sent: Monday, August 25, 2014 2:30 PM >> This patch contains driver for Renesas R-Car JPEG codec. >> Cnanges since v1: >> - s/g_fmt function simplified >> - default format for queues added >> - dumb vidioc functions added to be in compliance with standard >> api: >> jpu_s_priority, jpu_g_priority >> - standard v4l2_ctrl_subscribe_event and v4l2_event_unsubscribe >> now in use by the same reason > The patch looks good to me. However, I would suggest using the BIT macro I'd prefer not using it since the driver #define's not only bits but also bit values and multi-bit fields. Using BIT() would look inconsistent in this situation. > and making some short functions inline. I think the current trend is to use *inline* only in the headers, and let gcc figure it out itself in the .c files. >> Signed-off-by: Mikhail Ulyanov <mikhail.ulyanov@cogentembedded.com> [...] >> diff --git a/drivers/media/platform/jpu.c >> b/drivers/media/platform/jpu.c new file mode 100644 index >> 0000000..da70491 >> --- /dev/null >> +++ b/drivers/media/platform/jpu.c >> @@ -0,0 +1,1628 @@ [...] > Best wishes, WBR, Sergei -- To unsubscribe from this list: send the line "unsubscribe linux-sh" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Mikhail, Thank you for the patch. On Monday 25 August 2014 16:29:47 Mikhail Ulyanov wrote: > This patch contains driver for Renesas R-Car JPEG codec. > > Cnanges since v1: > - s/g_fmt function simplified > - default format for queues added > - dumb vidioc functions added to be in compliance with standard api: > jpu_s_priority, jpu_g_priority > - standard v4l2_ctrl_subscribe_event and v4l2_event_unsubscribe > now in use by the same reason > > Signed-off-by: Mikhail Ulyanov <mikhail.ulyanov@cogentembedded.com> > --- > drivers/media/platform/Kconfig | 11 + > drivers/media/platform/Makefile | 2 + > drivers/media/platform/jpu.c | 1628 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 1641 insertions(+) > create mode 100644 drivers/media/platform/jpu.c > > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig > index 6d86646..1b8c846 100644 > --- a/drivers/media/platform/Kconfig > +++ b/drivers/media/platform/Kconfig > @@ -220,6 +220,17 @@ config VIDEO_RENESAS_VSP1 > To compile this driver as a module, choose M here: the module > will be called vsp1. > > +config VIDEO_RENESAS_JPU > + tristate "Renesas JPEG Processing Unit" > + depends on VIDEO_DEV && VIDEO_V4L2 > + 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 jpu. > + You could move this above VIDEO_RENESAS_VSP1 to keep the file somehow sorted. Same comment for the Makefile. > config VIDEO_TI_VPE > tristate "TI VPE (Video Processing Engine) driver" > depends on VIDEO_DEV && VIDEO_V4L2 && SOC_DRA7XX > diff --git a/drivers/media/platform/Makefile > b/drivers/media/platform/Makefile index e5269da..e438534 100644 > --- a/drivers/media/platform/Makefile > +++ b/drivers/media/platform/Makefile > @@ -47,6 +47,8 @@ obj-$(CONFIG_SOC_CAMERA) += soc_camera/ > > obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ > > +obj-$(CONFIG_VIDEO_RENESAS_JPU) += jpu.o > + > obj-y += davinci/ > > obj-$(CONFIG_ARCH_OMAP) += omap/ > diff --git a/drivers/media/platform/jpu.c b/drivers/media/platform/jpu.c > new file mode 100644 > index 0000000..da70491 > --- /dev/null > +++ b/drivers/media/platform/jpu.c > @@ -0,0 +1,1628 @@ > +/* > + * Author: Mikhail Ulyanov <source@cogentembedded.com> > + * Copyright (C) 2014 Cogent Embedded, Inc. > + * Copyright (C) 2014 Renesas Electronics Corporation > + * > + * This is based on the drivers/media/platform/s5p-jpu driver by > + * Andrzej Pietrasiewicz and Jacek Anaszewski. > + * > + * 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 <linux/clk.h> > +#include <linux/err.h> > +#include <linux/gfp.h> Do you really gfp.h ? slab.h should be enough. > +#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 JPU_M2M_NAME "jpu" > + > +#define JPU_WIDTH_MIN 16 > +#define JPU_HEIGHT_MIN 16 > +#define JPU_WIDTH_MAX 4096 > +#define JPU_HEIGHT_MAX 4096 > +#define JPU_DEFAULT_WIDTH 640 > +#define JPU_DEFAULT_HEIGHT 480 > + > +#define JPU_ENCODE 0 > +#define JPU_DECODE 1 How about making this an enum, or turning the ctx->mode field into a boolean compress (or encoder, decode, decompress...) field ? > +/* 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_SOI_DISABLE (1 << 8) > +#define JCMOD_SOI_ENABLE (0 << 8) > +#define JCMOD_PCTR (1 << 7) > +#define JCMOD_MSKIP_DISABLE (0 << 5) > +#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_BRST (1 << 7) > +#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 JCDRIU 0x14 /* JPEG code DRI upper register */ > +#define JCDRIL 0x18 /* JPEG code DRI lower register */ > +#define JCVSZU 0x1C /* JPEG code vertical size upper register */ The majority of v4l drivers tend to favour lowercase hex values, but as it's a small majority, I won't push for that :-) > +#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 > + > +/* JPEG interrupt status register */ > +#define JINTS 0x3C > +#define JINTS_MASK 0x7c68 > + > +#define INT(n) (1 << n) > + > +#define JCDERR 0x40 /* JPEG code decode error register */ > + > +/* JPEG interface encoding */ > +#define JIFECNT 0x70 > +#define JIFECNT_INFT_422 0 > +#define JIFECNT_INFT_420 1 > +#define JIFECNT_SWAP_WB (0x3 << 4) > + > +/* JPEG interface encode source Y address register 1 */ > +#define JIFESYA1 0x74 > +/* JPEG interface encode source C address register 1 */ > +#define JIFESCA1 0x78 > +/* JPEG interface encode source Y address register 2 */ > +#define JIFESYA2 0x7C > +/* JPEG interface encode source C address register 2 */ > +#define JIFESCA2 0x80 > +/* JPEG interface encode source memory width register */ > +#define JIFESMW 0x84 > +/* JPEG interface encode source vertical size register */ > +#define JIFESVSZ 0x88 > +/* JPEG interface encode source horizontal size register */ > +#define JIFESHSZ 0x8C > +/* JPEG interface encode destination address register 1 */ > +#define JIFEDA1 0x90 > +/* JPEG interface encode destination address register 2 */ > +#define JIFEDA2 0x94 > + > +/* JPEG decoding control register */ > +#define JIFDCNT 0xA0 > +#define JIFDCNT_SWAP (3 << 1) > +#define JIFDCNT_SWAP_NO (0 << 1) > +#define JIFDCNT_SWAP_BYTE (1 << 1) > +#define JIFDCNT_SWAP_WORD (2 << 1) > +#define JIFDCNT_SWAP_WB (3 << 1) > + > +/* JPEG decode source address register 1 */ > +#define JIFDSA1 0xA4 > +/* JPEG decode source address register 2 */ > +#define JIFDSA2 0xA8 > +/* JPEG decode data reload size register */ > +#define JIFDDRSZ 0xAC > +/* JPEG decode data destination memory width register */ > +#define JIFDDMW 0xB0 > +/* JPEG decode data destination vertical size register */ > +#define JIFDDVSZ 0xB4 > +/* JPEG decode data destination horizontal size register */ > +#define JIFDDHSZ 0xB8 > +/* JPEG decode data destination Y address register 1 */ > +#define JIFDDYA1 0xBC > +/* JPEG decode data destination C address register 1 */ > +#define JIFDDCA1 0xC0 > +/* JPEG decode data destination Y address register 2 */ > +#define JIFDDYA2 0xC4 > +/* JPEG decode data destination C address register 2 */ > +#define JIFDDCA2 0xC8 > + > +/* JPEG code quantization tables registers */ > +#define JCQTBL(n) (0x10000 + (n) * 0x40) > + > +/* JPEG code Huffman table DC registers */ > +#define JCHTBD(n) (0x10100 + (n) * 0x100) > + > +/* JPEG code Huffman table AC registers */ > +#define JCHTBA(n) (0x10120 + (n) * 0x100) > + > +#define JPU_JPEG_HDR_SIZE 0x250 > + > +enum jpu_status { > + JPU_OK, > + JPU_BUSY, > + JPU_ERR > +}; > + > +/** > + * struct jpu - JPEG IP abstraction > + * @mutex: the mutex protecting this structure > + * @slock: 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 > + * @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 > + * @bounds: platform specific IP limitations > + * @wq: waitqueue for header parsing handling > + * @statatus: current driver state variable > + * @ref_counter: reference counter > + */ > +struct jpu { > + struct mutex mutex; > + spinlock_t slock; > + > + struct v4l2_device v4l2_dev; > + struct video_device *vfd_encoder; > + struct video_device *vfd_decoder; > + struct v4l2_m2m_dev *m2m_dev; > + > + void __iomem *regs; > + unsigned int irq; > + struct clk *clk; > + struct device *dev; > + void *alloc_ctx; > + struct jpu_bounds *bounds; > + wait_queue_head_t wq; > + enum jpu_status status; > + int ref_count; > +}; > + > +/** > + * struct jpu_fmt - driver's internal format data > + * @name: format descritpion > + * @fourcc: the fourcc code, 0 if not applicable > + * @colorspace: the colorspace specificator > + * @depth: number of bits per pixel > + * @h_align: horizontal alignment order (align to 2^h_align) > + * @v_align: vertical alignment order (align to 2^v_align) > + * @types: types of queue this format is applicable to > + */ > +struct jpu_fmt { > + char *name; > + u32 fourcc; > + u32 colorspace; > + int depth; > + int h_align; > + int v_align; > + u32 types; > +}; > + > +/** > + * jpu_q_data - parameters of one queue > + * @fmt: driver-specific format of this queue > + * @default_fmt: default format of this queue > + * @w: image width > + * @h: image height > + * @size: image buffer size in bytes > + */ > +struct jpu_q_data { > + struct jpu_fmt *fmt; > + struct jpu_fmt *default_fmt; > + u32 w; > + u32 h; > + u32 size; > +}; > + > +/** > + * jpu_ctx - the device context data > + * @jpu: JPEG IP device for this context > + * @mode: compression (encode) operation or decompression (decode) > + * @compr_quality: destination image quality in compression (encode) mode > + * @out_q: source (output) queue information > + * @fh: file handler; > + * @hdr_parsed: set if header has been parsed during decompression > + * @ctrl_handler: controls handler > + */ > +struct jpu_ctx { > + struct jpu *jpu; > + unsigned int mode; > + unsigned short compr_quality; > + struct jpu_q_data out_q; > + struct jpu_q_data cap_q; > + struct v4l2_fh fh; > + bool hdr_parsed; > + struct v4l2_ctrl_handler ctrl_handler; > +}; > + > +static struct jpu_fmt jpu_formats[] = { > + { > + .name = "JPEG JFIF", > + .fourcc = V4L2_PIX_FMT_JPEG, > + .colorspace = V4L2_COLORSPACE_JPEG, > + .types = JPU_ENC_CAPTURE | JPU_DEC_OUTPUT, > + }, > + { > + .name = "YUV 4:2:2 semiplanar, YCbCr", > + .fourcc = V4L2_PIX_FMT_NV16, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .depth = 16, > + .h_align = 3, > + .v_align = 3, > + .types = JPU_ENC_OUTPUT, > + }, > + { > + .name = "YUV 4:2:0 semiplanar, YCbCr", > + .fourcc = V4L2_PIX_FMT_NV12, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .depth = 12, > + .h_align = 3, > + .v_align = 3, > + .types = JPU_ENC_OUTPUT, > + }, > + { > + .name = "YUV 4:2:2 semiplanar, YCbCr", > + .fourcc = V4L2_PIX_FMT_NV16, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .depth = 16, > + .h_align = 2, > + .v_align = 2, > + .types = JPU_DEC_CAPTURE, > + }, > + { > + .name = "YUV 4:2:0 semiplanar, YCbCr", > + .fourcc = V4L2_PIX_FMT_NV12, > + .colorspace = V4L2_COLORSPACE_SRGB, > + .depth = 12, > + .h_align = 2, > + .v_align = 2, > + .types = 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 > +}; > + > +static unsigned char hdr_blob0[] = { > + 0xff, 0xd8, 0xff, 0xfe, 0x00, 0x0e, 0x00, 0x00, > + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, > + 0x00, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x84 > +}; > + > +static unsigned char hdr_blob1[] = { > + 0xff, 0xc0, 0x00, 0x11, 0x08 > +}; > + > +static unsigned char hdr_blob2[] = { > + 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, > + 0xc4, 0x01, 0xa2 > +}; > + > +static const unsigned int qtbl_lum[2][16] = { > + { > + 0x14401927, 0x322e3e44, 0x10121726, 0x26354144, > + 0x19171f26, 0x35414444, 0x27262635, 0x41444444, > + 0x32263541, 0x44444444, 0x2e354144, 0x44444444, > + 0x3e414444, 0x44444444, 0x44444444, 0x44444444 > + }, > + { > + 0x08060608, 0x0c0e1011, 0x06060608, 0x0a0d0c0f, > + 0x06060708, 0x0d0e1218, 0x0808080e, 0x0d131823, > + 0x0c0a0d0d, 0x141a2227, 0x0e0d0e13, 0x1a222727, > + 0x100c1318, 0x22272727, 0x110f1823, 0x27272727 > + } > +}; > + > +static const unsigned int qtbl_chr[2][16] = { > + { > + 0x15192026, 0x36444444, 0x191c1826, 0x36444444, > + 0x2018202b, 0x42444444, 0x26262b35, 0x44444444, > + 0x36424444, 0x44444444, 0x44444444, 0x44444444, > + 0x44444444, 0x44444444, 0x44444444, 0x44444444 > + }, > + { > + 0x0908090b, 0x0e111318, 0x080a090b, 0x0e0d1116, > + 0x09090d0e, 0x0d0f171a, 0x0b0b0e0e, 0x0f141a21, > + 0x0e0e0d0f, 0x14182127, 0x110d0f14, 0x18202727, > + 0x1311171a, 0x21272727, 0x18161a21, 0x27272727 > + } > +}; > + > +static const unsigned int hdctbl_lum[7] = { > + 0x00010501, 0x01010101, 0x01000000, 0x00000000, > + 0x00010203, 0x04050607, 0x08090a0b > +}; > + > +static const unsigned int hdctbl_chr[7] = { > + 0x00030101, 0x01010101, 0x01010100, 0x00000000, > + 0x00010203, 0x04050607, 0x08090a0b > +}; > + > +static const unsigned int hactbl_lum[45] = { > + 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[45] = { > + 0x00020102, 0x04040304, 0x07050404, 0x00010277, > + 0x00010203, 0x11040521, 0x31061241, 0x51076171, > + 0x13223281, 0x08144291, 0xa1b1c109, 0x233352f0, > + 0x156372d1, 0x0a162434, 0xe125f117, 0x18191a26, > + 0x2728292a, 0x35363738, 0x393a4344, 0x45464748, > + 0x494a5354, 0x55565758, 0x595a6364, 0x65666768, > + 0x696a7374, 0x75767778, 0x797a8283, 0x84858687, > + 0x88898a92, 0x93949596, 0x9798999a, 0xa2a3a4a5, > + 0xa6a7a8a9, 0xaab2b3b4, 0xb5b6b7b8, 0xb9bac2c3, > + 0xc4c5c6c7, 0xc8c9cad2, 0xd3d4d5d6, 0xd7d8d9da, > + 0xe2e3e4e5, 0xe6e7e8e9, 0xeaf2f3f4, 0xf5f6f7f8, > + 0xf9fa0000 > +}; > + > +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(void __iomem *regs, const unsigned int *tbl, > + int len) { > + unsigned int i; > + > + for (i = 0; i < len; i++) > + iowrite32(tbl[i], regs + (i << 2)); > +} > + > +static void jpu_set_qtbl(void __iomem *regs, int quality) > +{ > + jpu_set_tbl(regs + JCQTBL(0), qtbl_lum[quality], > + ARRAY_SIZE(qtbl_lum[quality])); > + jpu_set_tbl(regs + JCQTBL(1), qtbl_chr[quality], > + ARRAY_SIZE(qtbl_chr[quality])); > +} > + > +static void jpu_set_htbl(void __iomem *regs) > +{ > + jpu_set_tbl(regs + JCHTBD(0), hdctbl_lum, ARRAY_SIZE(hdctbl_lum)); > + jpu_set_tbl(regs + JCHTBD(1), hdctbl_chr, ARRAY_SIZE(hdctbl_chr)); > + jpu_set_tbl(regs + JCHTBA(0), hactbl_lum, ARRAY_SIZE(hactbl_lum)); > + jpu_set_tbl(regs + JCHTBA(1), hactbl_chr, ARRAY_SIZE(hactbl_chr)); > +} > + > +static void jpu_int_clear(void __iomem *regs, unsigned int int_status) > +{ > + iowrite32(~int_status & JINTS_MASK, regs + JINTS); > + > + if (int_status & (INT(6) | INT(5) | INT(3))) > + iowrite32(JCCMD_JEND, regs + JCCMD); > +} > + > +static void jpu_reset(void __iomem *regs) > +{ > + iowrite32(JCCMD_SRST, regs + JCCMD); > + while ((ioread32(regs + JCCMD) & JCCMD_SRST) != 0) > + cpu_relax(); How about adding a timeout ? > +} > + > +/* > + * > =========================================================================== > = + * video ioctl operations > + * > =========================================================================== > = + */ > +static void put_byte(unsigned long *p, u8 v) > +{ > + u8 *addr = (u8 *)*p; > + > + *addr = v; > + (*p)++; > +} > + > +static void put_short_be(unsigned long *p, u16 v) > +{ > + u16 *addr = (u16 *)*p; > + > + *addr = cpu_to_be16(v); > + *p += 2; > +} > + > +static void put_word_be(unsigned long *p, u32 v) > +{ > + u32 *addr = (u32 *)*p; > + > + *addr = cpu_to_be32(v); > + *p += 4; > +} > + > +static void put_blob_byte(unsigned long *p, const unsigned char *blob, > + unsigned int len) > +{ > + int i; len is an unsigned int, i should be as well. Same comment for the signed loop indices in the rest of the driver. > + > + for (i = 0; i < len; i++) > + put_byte(p, blob[i]); Wouldn't a memcpy be more efficient ? > +} > + > +static void put_qtbl(unsigned long *p, unsigned char id, > + const unsigned int *qtbl) > +{ > + int i; > + > + put_byte(p, id); > + for (i = 0; i < ARRAY_SIZE(zigzag); i++) > + put_byte(p, *((u8 *)qtbl + zigzag[i])); > +} > + > +static void put_htbl(unsigned long *p, unsigned char tc, > + const unsigned int *htbl, unsigned int len) > +{ > + int i; > + > + put_byte(p, tc); > + for (i = 0; i < len; i++) > + put_word_be(p, htbl[i]); > +} > + > +static void jpu_generate_hdr(struct jpu_q_data *q, int quality, void > *buffer) +{ > + /* SOI(2) - DQT(134 / 2 tables) - SOF0(19) - DHT(420 / 2 tables) */ > + unsigned long p = (unsigned long)buffer; > + > + put_blob_byte(&p, hdr_blob0, ARRAY_SIZE(hdr_blob0)); > + > + put_qtbl(&p, 0x00, qtbl_lum[quality]); > + put_qtbl(&p, 0x01, qtbl_chr[quality]); > + > + put_blob_byte(&p, hdr_blob1, ARRAY_SIZE(hdr_blob1)); > + > + put_short_be(&p, q->h); > + put_short_be(&p, q->w); > + > + put_byte(&p, 0x03); > + put_byte(&p, 0x01); > + > + if (q->fmt->fourcc == V4L2_PIX_FMT_NV16) > + put_byte(&p, 0x21); > + else > + put_byte(&p, 0x22); > + > + put_blob_byte(&p, hdr_blob2, ARRAY_SIZE(hdr_blob2)); > + > + put_htbl(&p, 0x00, hdctbl_lum, ARRAY_SIZE(hdctbl_lum)); > + put_htbl(&p, 0x10, hactbl_lum, ARRAY_SIZE(hactbl_lum)); > + p -= 2; > + > + put_htbl(&p, 0x01, hdctbl_chr, ARRAY_SIZE(hdctbl_chr)); > + put_htbl(&p, 0x11, hactbl_chr, ARRAY_SIZE(hactbl_chr)); Wouldn't it be more efficient to memcpy the whole header in one go and then patch the width, height and subsampling ? You could get rid of the endianness conversions that way. > +} > + > +static int jpu_querycap(struct file *file, void *priv, > + struct v4l2_capability *cap) > +{ > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + > + if (ctx->mode == JPU_ENCODE) { > + strlcpy(cap->driver, JPU_M2M_NAME " encoder", > + sizeof(cap->driver)); > + strlcpy(cap->card, JPU_M2M_NAME " encoder", > + sizeof(cap->card)); > + } else { > + strlcpy(cap->driver, JPU_M2M_NAME " decoder", > + sizeof(cap->driver)); > + strlcpy(cap->card, JPU_M2M_NAME " decoder", > + sizeof(cap->card)); > + } > + 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; > + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; > + return 0; > +} > + > +static struct jpu_fmt *jpu_find_format(unsigned int mode, u32 pixelformat, > + unsigned int fmt_type) > +{ > + unsigned int k, fmt_flag; > + > + if (mode == JPU_ENCODE) > + 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 (k = 0; k < ARRAY_SIZE(jpu_formats); k++) { Anything wrong with i as a loop index ? :-) > + struct jpu_fmt *fmt = &jpu_formats[k]; > + > + if (fmt->fourcc == pixelformat && fmt->types & fmt_flag) > + return fmt; > + } > + > + return NULL; > +} > + > +static int jpu_enum_fmt(struct v4l2_fmtdesc *f, u32 type) > +{ > + int i, num = 0; > + > + for (i = 0; i < ARRAY_SIZE(jpu_formats); ++i) { > + if (jpu_formats[i].types & type) { > + /* index-th format of type type found ? */ > + if (num == f->index) > + break; > + /* > + * Correct type but haven't reached our index yet, > + * just increment per-type index > + */ > + ++num; > + } > + } > + > + /* Format not found */ > + if (i >= ARRAY_SIZE(jpu_formats)) > + return -EINVAL; > + > + strlcpy(f->description, jpu_formats[i].name, sizeof(f->description)); > + f->pixelformat = jpu_formats[i].fourcc; > + > + return 0; > +} > + > +static int jpu_enum_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_fmtdesc *f) > +{ > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + > + if (ctx->mode == JPU_ENCODE) > + return jpu_enum_fmt(f, JPU_ENC_CAPTURE); > + > + return jpu_enum_fmt(f, JPU_DEC_CAPTURE); > +} > + > +static int jpu_enum_fmt_vid_out(struct file *file, void *priv, > + struct v4l2_fmtdesc *f) > +{ > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + > + if (ctx->mode == JPU_ENCODE) > + return jpu_enum_fmt(f, JPU_ENC_OUTPUT); > + > + return jpu_enum_fmt(f, JPU_DEC_OUTPUT); > +} > + > +static struct jpu_q_data *jpu_get_q_data(struct jpu_ctx *ctx, > + enum v4l2_buf_type type) > +{ > + switch (type) { > + case V4L2_BUF_TYPE_VIDEO_OUTPUT: > + return &ctx->out_q; > + case V4L2_BUF_TYPE_VIDEO_CAPTURE: > + return &ctx->cap_q; > + default: > + return NULL; Can the default case occur ? > + } > +} > + > +static int jpu_g_fmt(struct file *file, void *priv, struct v4l2_format *f) > +{ > + struct vb2_queue *vq; > + struct jpu_q_data *q_data; > + struct v4l2_pix_format *pix = &f->fmt.pix; > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + > + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); > + if (!vq) > + return -EINVAL; > + > + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && > + ctx->mode == JPU_DECODE && !ctx->hdr_parsed) > + return -EINVAL; I wonder if -EINVAL is the proper error code to be returned here. We want to express that the format isn't available yet. What do other m2m drivers return in this case ? > + q_data = jpu_get_q_data(ctx, f->type); > + if (!q_data) > + return -EINVAL; > + > + pix->width = q_data->w; > + pix->height = q_data->h; > + pix->field = V4L2_FIELD_NONE; > + pix->pixelformat = q_data->fmt->fourcc; > + pix->colorspace = q_data->fmt->colorspace; > + pix->bytesperline = 0; > + if (pix->pixelformat != V4L2_PIX_FMT_JPEG) > + pix->bytesperline = q_data->w; > + pix->sizeimage = q_data->size; > + > + return 0; > +} > + > + > +static void jpu_bound_align_image(u32 *w, unsigned int wmin, unsigned int > wmax, + unsigned int walign, u32 *h, > + unsigned int hmin, unsigned int hmax, > + unsigned int halign) > +{ > + int width, height, w_step, h_step; These should be unsigned. > + > + width = *w; > + height = *h; > + > + w_step = 1 << walign; > + h_step = 1 << halign; > + v4l_bound_align_image(w, wmin, wmax, walign, h, hmin, hmax, halign, 3); > + > + if (*w < width && *w + w_step < wmax) > + *w += w_step; > + if (*h < height && *h + h_step < hmax) > + *h += h_step; > +} > + > + > +static int jpu_try_fmt(struct file *file, void *priv, struct v4l2_format > *f) +{ > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + struct v4l2_pix_format *pix = &f->fmt.pix; > + struct jpu_fmt *fmt; > + struct jpu_q_data *q_data; > + unsigned int f_type; > + > + q_data = jpu_get_q_data(ctx, f->type); > + if (!q_data) > + return -EINVAL; > + > + f_type = V4L2_TYPE_IS_OUTPUT(f->type) ? JPU_FMT_TYPE_OUTPUT : > + JPU_FMT_TYPE_CAPTURE; > + > + fmt = jpu_find_format(ctx->mode, pix->pixelformat, f_type); > + if (!fmt) > + fmt = q_data->default_fmt; > + > + pix->pixelformat = fmt->fourcc; > + 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); > + > + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) { > + if (pix->sizeimage <= 0) > + pix->sizeimage = PAGE_SIZE; PAGE_SIZE, really ? > + pix->bytesperline = 0; > + } else { > + if (pix->bytesperline < pix->width) > + pix->bytesperline = pix->width; Shouldn't bytesperline take the depth into account ? > + pix->sizeimage = (pix->width * pix->height * fmt->depth) >> 3; sizeimage should be bytesperline * pix->height, in case bytesperline is larger than the minimum value. > + } > + > + pix->field = V4L2_FIELD_NONE; > + pix->colorspace = fmt->colorspace; > + > + return 0; > +} > + > +static int jpu_s_fmt(struct file *file, void *priv, struct v4l2_format *f) > +{ > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + struct v4l2_pix_format *pix = &f->fmt.pix; > + struct vb2_queue *vq; > + struct jpu_q_data *q_data; > + unsigned int f_type; > + int ret; > + > + ret = jpu_try_fmt(file, priv, f); > + if (ret) > + return ret; > + > + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); > + if (!vq) > + return -EINVAL; > + > + q_data = jpu_get_q_data(ctx, f->type); > + if (!q_data) > + return -EINVAL; > + > + if (vb2_is_busy(vq)) { > + v4l2_err(&ctx->jpu->v4l2_dev, "%s queue busy\n", __func__); > + return -EBUSY; > + } > + > + f_type = V4L2_TYPE_IS_OUTPUT(f->type) ? JPU_FMT_TYPE_OUTPUT : > + JPU_FMT_TYPE_CAPTURE; > + > + q_data->fmt = jpu_find_format(ctx->mode, pix->pixelformat, f_type); > + q_data->w = pix->width; > + q_data->h = pix->height; > + if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) > + q_data->size = q_data->w * q_data->h * q_data->fmt->depth >> 3; > + else > + q_data->size = pix->sizeimage; > + > + return 0; > +} > + > +static int jpu_g_selection(struct file *file, void *priv, > + struct v4l2_selection *s) > +{ > + struct jpu_ctx *ctx = fh_to_ctx(priv); > + > + return -ENOTTY; A leftover ? > + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && > + s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) > + return -EINVAL; > + > + switch (s->target) { > + case V4L2_SEL_TGT_CROP: > + case V4L2_SEL_TGT_CROP_BOUNDS: > + case V4L2_SEL_TGT_CROP_DEFAULT: > + case V4L2_SEL_TGT_COMPOSE: > + case V4L2_SEL_TGT_COMPOSE_DEFAULT: > + s->r.width = ctx->out_q.w; > + s->r.height = ctx->out_q.h; > + break; > + case V4L2_SEL_TGT_COMPOSE_BOUNDS: > + case V4L2_SEL_TGT_COMPOSE_PADDED: > + s->r.width = ctx->cap_q.w; > + s->r.height = ctx->cap_q.h; > + break; > + default: > + return -EINVAL; > + } > + s->r.left = 0; > + s->r.top = 0; > + 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->slock, flags); > + > + if (ctrl->id == V4L2_CID_JPEG_COMPRESSION_QUALITY) > + ctx->compr_quality = ctrl->val; > + > + spin_unlock_irqrestore(&ctx->jpu->slock, flags); > + return 0; > +} > + > +static int jpu_g_priority(struct file *file, void *priv, enum v4l2_priority > *p) +{ > + return -ENOTTY; > +} > + > +static int jpu_s_priority(struct file *file, void *priv, enum v4l2_priority > p) +{ > + return -ENOTTY; > +} Can't you just leave the operations unimplemented ? > +static const struct v4l2_ctrl_ops jpu_ctrl_ops = { > + .s_ctrl = jpu_s_ctrl, > +}; > + > +static const struct v4l2_ioctl_ops jpu_ioctl_ops = { > + .vidioc_querycap = jpu_querycap, > + > + .vidioc_enum_fmt_vid_cap = jpu_enum_fmt_vid_cap, > + .vidioc_enum_fmt_vid_out = jpu_enum_fmt_vid_out, > + > + .vidioc_g_fmt_vid_cap = jpu_g_fmt, > + .vidioc_g_fmt_vid_out = jpu_g_fmt, > + > + .vidioc_try_fmt_vid_cap = jpu_try_fmt, > + .vidioc_try_fmt_vid_out = jpu_try_fmt, > + > + .vidioc_s_fmt_vid_cap = jpu_s_fmt, > + .vidioc_s_fmt_vid_out = jpu_s_fmt, > + > + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, > + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, > + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, > + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, > + .vidioc_streamon = v4l2_m2m_ioctl_streamon, > + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, > + > + .vidioc_g_selection = jpu_g_selection, > + .vidioc_s_priority = jpu_s_priority, > + .vidioc_g_priority = jpu_g_priority, > + .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; > + > + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); You have a single control, so you could use 1 instead of 3. > + if (ctx->mode == JPU_ENCODE) { > + v4l2_ctrl_new_std(&ctx->ctrl_handler, &jpu_ctrl_ops, > + V4L2_CID_JPEG_COMPRESSION_QUALITY, > + 0, 1, 1, 1); > + } > + > + if (ctx->ctrl_handler.error) > + return ctx->ctrl_handler.error; > + > + if (ctx->mode == JPU_DECODE) > + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | > + V4L2_CTRL_FLAG_READ_ONLY; ctrl isn't initialized. > + return 0; > +} > + > +/* > + * > =========================================================================== > = + * 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 size, count = *nbuffers; > + > + q_data = jpu_get_q_data(ctx, vq->type); > + if (q_data == NULL) > + return -EINVAL; > + > + size = q_data->size; > + > + /* > + * Header is parsed during decoding and parsed information stored > + * in the context so we do not allow another buffer to overwrite it > + */ > + if (ctx->mode == JPU_DECODE) > + count = 1; > + > + *nbuffers = count; > + *nplanes = 1; > + sizes[0] = size; > + alloc_ctxs[0] = 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; > + > + q_data = jpu_get_q_data(ctx, vb->vb2_queue->type); > + if (q_data == NULL) > + return -EINVAL; > + > + if (vb2_plane_size(vb, 0) < q_data->size) { > + pr_err("%s: data will not fit into plane (%lu < %lu)\n", > + __func__, vb2_plane_size(vb, 0), (long)q_data->size); > + return -EINVAL; > + } > + > + vb2_set_plane_payload(vb, 0, q_data->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->mode == JPU_DECODE && > + vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { > + struct jpu_q_data *q_data; > + struct jpu *jpu = ctx->jpu; > + unsigned int subsampling, w_out, h_out, w_cap, h_cap; > + unsigned long src_addr, flags; > + > + src_addr = vb2_dma_contig_plane_dma_addr(vb, 0); > + > + jpu_reset(jpu->regs); > + > + iowrite32(JCMOD_DSP_DEC | JCMOD_PCTR, jpu->regs + JCMOD); > + iowrite32(JIFECNT_SWAP_WB, jpu->regs + JIFECNT); > + iowrite32(JIFDCNT_SWAP_WB, jpu->regs + JIFDCNT); > + > + spin_lock_irqsave(&jpu->slock, flags); > + jpu->status = JPU_BUSY; > + spin_unlock_irqrestore(&ctx->jpu->slock, flags); > + > + iowrite32(INT(3), jpu->regs + JINTE); > + iowrite32(src_addr, jpu->regs + JIFDSA1); > + iowrite32(JCCMD_JSRT, jpu->regs + JCCMD); > + > + if (wait_event_interruptible_timeout(jpu->wq, > + jpu->status != JPU_BUSY, > + msecs_to_jiffies(100)) > 0 > + && jpu->status == JPU_OK) > + ctx->hdr_parsed = true; > + > + w_out = ioread32(jpu->regs + JCHSZU) << 8 | > + ioread32(jpu->regs + JCHSZD); > + > + h_out = ioread32(jpu->regs + JCVSZU) << 8 | > + ioread32(jpu->regs + JCVSZD); > + > + w_cap = ioread32(jpu->regs + JIFDDHSZ); > + h_cap = ioread32(jpu->regs + JIFDDVSZ); > + > + switch (ioread32(jpu->regs + JCMOD) & JCMOD_REDU) { > + case JCMOD_REDU_422: > + subsampling = V4L2_PIX_FMT_NV16; > + break; > + case JCMOD_REDU_420: > + subsampling = V4L2_PIX_FMT_NV12; > + break; > + default: > + subsampling = 0; > + } > + > + if (!ctx->hdr_parsed || !subsampling || w_out > JPU_WIDTH_MAX || > + w_out < JPU_WIDTH_MIN || h_out > JPU_HEIGHT_MAX || > + h_out < JPU_HEIGHT_MIN) { > + pr_err("incompatible or corrupted JPEG data\n"); > + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); > + return; > + } > + > + q_data = &ctx->out_q; > + q_data->w = w_out; > + q_data->h = h_out; > + > + q_data = &ctx->cap_q; > + q_data->w = w_cap; > + q_data->h = h_cap; > + > + q_data->fmt = jpu_find_format(JPU_DECODE, subsampling, > + JPU_FMT_TYPE_CAPTURE); > + > + q_data->size = > + (q_data->w * q_data->h * q_data->fmt->depth) >> 3; > + } > + > + if (ctx->fh.m2m_ctx) > + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); > +} > + > +static int jpu_start_streaming(struct vb2_queue *q, unsigned int count) > +{ > + return 0; > +} > + > +static void jpu_stop_streaming(struct vb2_queue *q) > +{ > +} > + > +static struct vb2_ops jpu_qops = { > + .queue_setup = jpu_queue_setup, > + .buf_prepare = jpu_buf_prepare, > + .buf_queue = jpu_buf_queue, > + .wait_prepare = vb2_ops_wait_prepare, > + .wait_finish = vb2_ops_wait_finish, > + .start_streaming = jpu_start_streaming, > + .stop_streaming = jpu_stop_streaming, > +}; > + > +static int jpu_queue_init(void *priv, struct vb2_queue *src_vq, > + struct vb2_queue *dst_vq) > +{ > + struct jpu_ctx *ctx = priv; > + int ret; > + > + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; > + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; Any reason not to enable DMABUF (here and for the destination queue) ? I'd like to encourage userspace to move from USERPTR to DMABUF. > + src_vq->drv_priv = ctx; > + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_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; > + > + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; > + dst_vq->drv_priv = ctx; > + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_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; > + struct jpu_q_data *out_q, *cap_q; > + int ret = 0; No need to initialize ret to 0. > + > + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); > + if (!ctx) > + return -ENOMEM; > + > + if (mutex_lock_interruptible(&jpu->mutex)) { > + ret = -ERESTARTSYS; > + goto free; > + } > + > + v4l2_fh_init(&ctx->fh, vfd); > + ctx->fh.ctrl_handler = &ctx->ctrl_handler; > + file->private_data = &ctx->fh; > + v4l2_fh_add(&ctx->fh); > + > + out_q = &ctx->out_q; > + cap_q = &ctx->cap_q; > + > + out_q->w = JPU_DEFAULT_WIDTH; > + out_q->h = JPU_DEFAULT_HEIGHT; > + > + cap_q->w = JPU_DEFAULT_WIDTH; > + cap_q->h = JPU_DEFAULT_HEIGHT; > + > + ctx->jpu = jpu; > + if (vfd == jpu->vfd_encoder) { > + ctx->mode = JPU_ENCODE; > + out_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_NV16, > + JPU_FMT_TYPE_OUTPUT); > + cap_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_JPEG, > + JPU_FMT_TYPE_CAPTURE); > + out_q->size = (out_q->w * out_q->h * out_q->fmt->depth) >> 3; > + cap_q->size = PAGE_SIZE; > + } else { > + ctx->mode = JPU_DECODE; > + out_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_JPEG, > + JPU_FMT_TYPE_OUTPUT); > + cap_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_NV16, > + JPU_FMT_TYPE_CAPTURE); > + out_q->size = PAGE_SIZE; > + cap_q->size = (cap_q->w * cap_q->h * cap_q->fmt->depth) >> 3; > + } > + > + out_q->default_fmt = out_q->fmt; > + cap_q->default_fmt = cap_q->fmt; > + > + 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 error; > + } > + > + ret = jpu_controls_create(ctx); > + if (ret < 0) > + goto error; > + > + if (jpu->ref_count == 0) { > + ret = clk_prepare_enable(jpu->clk); > + if (ret < 0) > + goto error; > + } > + > + jpu->ref_count++; > + > + mutex_unlock(&jpu->mutex); > + return 0; > + > +error: > + v4l2_fh_del(&ctx->fh); > + v4l2_fh_exit(&ctx->fh); > + mutex_unlock(&jpu->mutex); > +free: > + 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); > + 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); > + mutex_unlock(&jpu->mutex); > + > + 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_device_run(void *priv) > +{ > + struct jpu_ctx *ctx = priv; > + struct jpu *jpu = ctx->jpu; > + struct vb2_buffer *src_buf, *dst_buf; > + unsigned long src_addr, dst_addr, flags; > + void *dst_vaddr; > + unsigned int w, h; > + > + spin_lock_irqsave(&ctx->jpu->slock, flags); Can this function be called from a context where interrupts are disabled ? If not spin_lock_irq is enough. > + > + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); > + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); > + src_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); > + dst_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); > + dst_vaddr = vb2_plane_vaddr(dst_buf, 0); > + > + jpu_reset(jpu->regs); > + > + if (ctx->mode == JPU_ENCODE) { > + unsigned int redu, inft; > + > + w = ctx->out_q.w; > + h = ctx->out_q.h; > + > + jpu_generate_hdr(&ctx->out_q, ctx->compr_quality, dst_vaddr); > + > + if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_NV12) { > + redu = JCMOD_REDU_420; > + inft = JIFECNT_INFT_420; > + } else { > + redu = JCMOD_REDU_422; > + inft = JIFECNT_INFT_422; > + } > + > + /* the only no marker mode works for encoding */ > + iowrite32(JCMOD_DSP_ENC | JCMOD_PCTR | redu | JCMOD_SOI_ENABLE | > + JCMOD_MSKIP_ENABLE, jpu->regs + JCMOD); > + > + iowrite32(JIFECNT_SWAP_WB | inft, jpu->regs + JIFECNT); > + iowrite32(JIFDCNT_SWAP_WB, jpu->regs + JIFDCNT); > + iowrite32(INT(10), jpu->regs + JINTE); > + > + /* Y and C components source addresses */ > + iowrite32(src_addr, jpu->regs + JIFESYA1); > + iowrite32(src_addr + w * h, jpu->regs + JIFESCA1); > + > + /* memory width */ > + iowrite32(w, jpu->regs + JIFESMW); > + > + iowrite32((w >> 8) & JCSZ_MASK, jpu->regs + JCHSZU); > + iowrite32(w & JCSZ_MASK, jpu->regs + JCHSZD); > + > + iowrite32((h >> 8) & JCSZ_MASK, jpu->regs + JCVSZU); > + iowrite32(h & JCSZ_MASK, jpu->regs + JCVSZD); > + > + iowrite32(w, jpu->regs + JIFESHSZ); > + iowrite32(h, jpu->regs + JIFESVSZ); > + > + iowrite32(dst_addr + JPU_JPEG_HDR_SIZE, jpu->regs + JIFEDA1); > + > + iowrite32(0 << JCQTN_SHIFT(1) | 1 << JCQTN_SHIFT(2) | > + 1 << JCQTN_SHIFT(3), jpu->regs + JCQTN); > + > + iowrite32(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), > + jpu->regs + JCHTN); > + > + jpu_set_qtbl(jpu->regs, ctx->compr_quality); > + jpu_set_htbl(jpu->regs); > + } else { > + w = ctx->cap_q.w; > + h = ctx->cap_q.h; > + > + iowrite32(JCMOD_DSP_DEC | JCMOD_PCTR, jpu->regs + JCMOD); > + iowrite32(JIFECNT_SWAP_WB, jpu->regs + JIFECNT); > + iowrite32(JIFDCNT_SWAP_WB, jpu->regs + JIFDCNT); > + iowrite32(INT(10) | INT(7) | INT(6) | INT(5), > + jpu->regs + JINTE); > + iowrite32(src_addr, jpu->regs + JIFDSA1); > + iowrite32(w, jpu->regs + JIFDDMW); > + iowrite32(dst_addr, jpu->regs + JIFDDYA1); > + iowrite32(dst_addr + w * h, jpu->regs + JIFDDCA1); > + } > + > + iowrite32(JCCMD_JSRT, jpu->regs + JCCMD); > + spin_unlock_irqrestore(&ctx->jpu->slock, flags); > +} > + > +static int jpu_job_ready(void *priv) > +{ > + struct jpu_ctx *ctx = priv; > + > + if (ctx->mode == JPU_DECODE) > + return ctx->hdr_parsed; > + return 1; > +} > + > +static void jpu_job_abort(void *priv) > +{ > +} > + > +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 long payload_size = 0; > + unsigned int int_status; > + unsigned int error; > + > + spin_lock(&jpu->slock); > + > + int_status = ioread32(jpu->regs + JINTS); > + jpu_int_clear(jpu->regs, int_status); > + > + /* > + * In any mode (decoding/encoding) we can additionaly get > + * error status (5th bit) > + * jpu operation complete status (6th bit) > + */ > + if (!((ioread32(jpu->regs + JINTE) | INT(5) | INT(6)) & int_status)) { Could you create macros for the interrupt status bits instead of hardcoding them ? > + spin_unlock(&jpu->slock); > + return IRQ_NONE; > + } > + > + if (jpu->status == JPU_BUSY) { > + if (int_status & INT(3)) > + jpu->status = JPU_OK; > + if (int_status & INT(5)) > + jpu->status = JPU_ERR; > + wake_up_interruptible(&jpu->wq); > + goto handled; > + } > + > + if ((int_status & INT(6)) && !(int_status & INT(10))) > + goto handled; > + > + curr_ctx = v4l2_m2m_get_curr_priv(jpu->m2m_dev); > + > + 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 & INT(10)) { > + if (curr_ctx->mode == JPU_ENCODE) { > + payload_size = ioread32(jpu->regs + JCDTCU) << 16 | > + ioread32(jpu->regs + JCDTCM) << 8 | > + ioread32(jpu->regs + JCDTCD); > + vb2_set_plane_payload(dst_buf, 0, > + payload_size + JPU_JPEG_HDR_SIZE); > + } > + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); > + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); > + } else if (int_status & INT(5)) { > + error = ioread32(jpu->regs + JCDERR); > + dev_err(jpu->dev, "error 0x%X\n", error); > + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); > + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); > + } > + > + v4l2_m2m_job_finish(jpu->m2m_dev, curr_ctx->fh.m2m_ctx); > + > +handled: > + spin_unlock(&jpu->slock); > + return IRQ_HANDLED; > +} > + > +/* > + * > =========================================================================== > = + * Driver basic infrastructure > + * > =========================================================================== > = + */ > +static const struct of_device_id jpu_dt_ids[] = { > + { .compatible = "renesas,jpu-r8a7790" }, > + { .compatible = "renesas,jpu-r8a7791" }, > + { .compatible = "renesas,jpu-gen2" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, jpu_dt_ids); > + > +static int jpu_probe(struct platform_device *pdev) > +{ > + struct jpu *jpu; > + struct resource *res; > + int ret; > + > + jpu = devm_kzalloc(&pdev->dev, sizeof(struct jpu), GFP_KERNEL); > + if (!jpu) > + return -ENOMEM; > + > + mutex_init(&jpu->mutex); > + spin_lock_init(&jpu->slock); > + 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"); > + ret = PTR_ERR(jpu->clk); > + return ret; > + } > + > + /* v4l2 device */ > + ret = v4l2_device_register(&pdev->dev, &jpu->v4l2_dev); > + if (ret) { > + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); > + goto clk_get_rollback; > + } > + > + /* 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; > + } > + > + /* JPEG encoder /dev/videoX node */ > + jpu->vfd_encoder = video_device_alloc(); > + if (!jpu->vfd_encoder) { > + v4l2_err(&jpu->v4l2_dev, "Failed to allocate video device\n"); > + ret = -ENOMEM; > + goto vb2_allocator_rollback; > + } > + strlcpy(jpu->vfd_encoder->name, JPU_M2M_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; > + 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 enc_vdev_alloc_rollback; > + } > + > + video_set_drvdata(jpu->vfd_encoder, jpu); > + v4l2_info(&jpu->v4l2_dev, "encoder device registered as /dev/video%d\n", > + jpu->vfd_encoder->num); > + > + /* JPEG decoder /dev/videoX node */ > + jpu->vfd_decoder = video_device_alloc(); > + if (!jpu->vfd_decoder) { > + v4l2_err(&jpu->v4l2_dev, "Failed to allocate video device\n"); > + ret = -ENOMEM; > + goto enc_vdev_register_rollback; > + } > + strlcpy(jpu->vfd_decoder->name, JPU_M2M_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; > + 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 dec_vdev_alloc_rollback; > + } > + > + video_set_drvdata(jpu->vfd_decoder, jpu); > + v4l2_info(&jpu->v4l2_dev, "decoder device registered as /dev/video%d\n", > + jpu->vfd_decoder->num); > + > + /* final statements & power management */ > + platform_set_drvdata(pdev, jpu); > + > + init_waitqueue_head(&jpu->wq); > + > + v4l2_info(&jpu->v4l2_dev, "Renesas JPEG codec\n"); > + > + return 0; > + > +dec_vdev_alloc_rollback: > + video_device_release(jpu->vfd_decoder); > + > +enc_vdev_register_rollback: > + video_unregister_device(jpu->vfd_encoder); > + > +enc_vdev_alloc_rollback: > + video_device_release(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); > + > +clk_get_rollback: > + clk_disable_unprepare(jpu->clk); I don't think this last call is needed. > + > + return ret; > +} > + > +static int jpu_remove(struct platform_device *pdev) > +{ > + struct jpu *jpu = platform_get_drvdata(pdev); > + > + video_unregister_device(jpu->vfd_decoder); > + video_device_release(jpu->vfd_decoder); > + video_unregister_device(jpu->vfd_encoder); > + video_device_release(jpu->vfd_encoder); > + vb2_dma_contig_cleanup_ctx(jpu->alloc_ctx); > + v4l2_m2m_release(jpu->m2m_dev); > + v4l2_device_unregister(&jpu->v4l2_dev); > + clk_disable_unprepare(jpu->clk); Neither is this one. > + > + 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) Shouldn't this be 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, > + .owner = THIS_MODULE, > + .name = JPU_M2M_NAME, > + .pm = &jpu_pm_ops, > + }, > +}; > + > +module_platform_driver(jpu_driver); > + > +MODULE_ALIAS("jpu"); > +MODULE_AUTHOR("Mikhail Ulianov <mikhail.ulyanov@cogentembedded.com>"); > +MODULE_DESCRIPTION("Renesas JPEG codec driver"); > +MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 6d86646..1b8c846 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -220,6 +220,17 @@ config VIDEO_RENESAS_VSP1 To compile this driver as a module, choose M here: the module will be called vsp1. +config VIDEO_RENESAS_JPU + tristate "Renesas JPEG Processing Unit" + depends on VIDEO_DEV && VIDEO_V4L2 + 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 jpu. + config VIDEO_TI_VPE tristate "TI VPE (Video Processing Engine) driver" depends on VIDEO_DEV && VIDEO_V4L2 && SOC_DRA7XX diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index e5269da..e438534 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -47,6 +47,8 @@ obj-$(CONFIG_SOC_CAMERA) += soc_camera/ obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ +obj-$(CONFIG_VIDEO_RENESAS_JPU) += jpu.o + obj-y += davinci/ obj-$(CONFIG_ARCH_OMAP) += omap/ diff --git a/drivers/media/platform/jpu.c b/drivers/media/platform/jpu.c new file mode 100644 index 0000000..da70491 --- /dev/null +++ b/drivers/media/platform/jpu.c @@ -0,0 +1,1628 @@ +/* + * Author: Mikhail Ulyanov <source@cogentembedded.com> + * Copyright (C) 2014 Cogent Embedded, Inc. + * Copyright (C) 2014 Renesas Electronics Corporation + * + * This is based on the drivers/media/platform/s5p-jpu driver by + * Andrzej Pietrasiewicz and Jacek Anaszewski. + * + * 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 <linux/clk.h> +#include <linux/err.h> +#include <linux/gfp.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 JPU_M2M_NAME "jpu" + +#define JPU_WIDTH_MIN 16 +#define JPU_HEIGHT_MIN 16 +#define JPU_WIDTH_MAX 4096 +#define JPU_HEIGHT_MAX 4096 +#define JPU_DEFAULT_WIDTH 640 +#define JPU_DEFAULT_HEIGHT 480 + +#define JPU_ENCODE 0 +#define JPU_DECODE 1 + +/* 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_SOI_DISABLE (1 << 8) +#define JCMOD_SOI_ENABLE (0 << 8) +#define JCMOD_PCTR (1 << 7) +#define JCMOD_MSKIP_DISABLE (0 << 5) +#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_BRST (1 << 7) +#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 JCDRIU 0x14 /* JPEG code DRI upper register */ +#define JCDRIL 0x18 /* JPEG code DRI lower register */ +#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 + +/* JPEG interrupt status register */ +#define JINTS 0x3C +#define JINTS_MASK 0x7c68 + +#define INT(n) (1 << n) + +#define JCDERR 0x40 /* JPEG code decode error register */ + +/* JPEG interface encoding */ +#define JIFECNT 0x70 +#define JIFECNT_INFT_422 0 +#define JIFECNT_INFT_420 1 +#define JIFECNT_SWAP_WB (0x3 << 4) + +/* JPEG interface encode source Y address register 1 */ +#define JIFESYA1 0x74 +/* JPEG interface encode source C address register 1 */ +#define JIFESCA1 0x78 +/* JPEG interface encode source Y address register 2 */ +#define JIFESYA2 0x7C +/* JPEG interface encode source C address register 2 */ +#define JIFESCA2 0x80 +/* JPEG interface encode source memory width register */ +#define JIFESMW 0x84 +/* JPEG interface encode source vertical size register */ +#define JIFESVSZ 0x88 +/* JPEG interface encode source horizontal size register */ +#define JIFESHSZ 0x8C +/* JPEG interface encode destination address register 1 */ +#define JIFEDA1 0x90 +/* JPEG interface encode destination address register 2 */ +#define JIFEDA2 0x94 + +/* JPEG decoding control register */ +#define JIFDCNT 0xA0 +#define JIFDCNT_SWAP (3 << 1) +#define JIFDCNT_SWAP_NO (0 << 1) +#define JIFDCNT_SWAP_BYTE (1 << 1) +#define JIFDCNT_SWAP_WORD (2 << 1) +#define JIFDCNT_SWAP_WB (3 << 1) + +/* JPEG decode source address register 1 */ +#define JIFDSA1 0xA4 +/* JPEG decode source address register 2 */ +#define JIFDSA2 0xA8 +/* JPEG decode data reload size register */ +#define JIFDDRSZ 0xAC +/* JPEG decode data destination memory width register */ +#define JIFDDMW 0xB0 +/* JPEG decode data destination vertical size register */ +#define JIFDDVSZ 0xB4 +/* JPEG decode data destination horizontal size register */ +#define JIFDDHSZ 0xB8 +/* JPEG decode data destination Y address register 1 */ +#define JIFDDYA1 0xBC +/* JPEG decode data destination C address register 1 */ +#define JIFDDCA1 0xC0 +/* JPEG decode data destination Y address register 2 */ +#define JIFDDYA2 0xC4 +/* JPEG decode data destination C address register 2 */ +#define JIFDDCA2 0xC8 + +/* JPEG code quantization tables registers */ +#define JCQTBL(n) (0x10000 + (n) * 0x40) + +/* JPEG code Huffman table DC registers */ +#define JCHTBD(n) (0x10100 + (n) * 0x100) + +/* JPEG code Huffman table AC registers */ +#define JCHTBA(n) (0x10120 + (n) * 0x100) + +#define JPU_JPEG_HDR_SIZE 0x250 + +enum jpu_status { + JPU_OK, + JPU_BUSY, + JPU_ERR +}; + +/** + * struct jpu - JPEG IP abstraction + * @mutex: the mutex protecting this structure + * @slock: 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 + * @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 + * @bounds: platform specific IP limitations + * @wq: waitqueue for header parsing handling + * @statatus: current driver state variable + * @ref_counter: reference counter + */ +struct jpu { + struct mutex mutex; + spinlock_t slock; + + struct v4l2_device v4l2_dev; + struct video_device *vfd_encoder; + struct video_device *vfd_decoder; + struct v4l2_m2m_dev *m2m_dev; + + void __iomem *regs; + unsigned int irq; + struct clk *clk; + struct device *dev; + void *alloc_ctx; + struct jpu_bounds *bounds; + wait_queue_head_t wq; + enum jpu_status status; + int ref_count; +}; + +/** + * struct jpu_fmt - driver's internal format data + * @name: format descritpion + * @fourcc: the fourcc code, 0 if not applicable + * @colorspace: the colorspace specificator + * @depth: number of bits per pixel + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @types: types of queue this format is applicable to + */ +struct jpu_fmt { + char *name; + u32 fourcc; + u32 colorspace; + int depth; + int h_align; + int v_align; + u32 types; +}; + +/** + * jpu_q_data - parameters of one queue + * @fmt: driver-specific format of this queue + * @default_fmt: default format of this queue + * @w: image width + * @h: image height + * @size: image buffer size in bytes + */ +struct jpu_q_data { + struct jpu_fmt *fmt; + struct jpu_fmt *default_fmt; + u32 w; + u32 h; + u32 size; +}; + +/** + * jpu_ctx - the device context data + * @jpu: JPEG IP device for this context + * @mode: compression (encode) operation or decompression (decode) + * @compr_quality: destination image quality in compression (encode) mode + * @out_q: source (output) queue information + * @fh: file handler; + * @hdr_parsed: set if header has been parsed during decompression + * @ctrl_handler: controls handler + */ +struct jpu_ctx { + struct jpu *jpu; + unsigned int mode; + unsigned short compr_quality; + struct jpu_q_data out_q; + struct jpu_q_data cap_q; + struct v4l2_fh fh; + bool hdr_parsed; + struct v4l2_ctrl_handler ctrl_handler; +}; + +static struct jpu_fmt jpu_formats[] = { + { + .name = "JPEG JFIF", + .fourcc = V4L2_PIX_FMT_JPEG, + .colorspace = V4L2_COLORSPACE_JPEG, + .types = JPU_ENC_CAPTURE | JPU_DEC_OUTPUT, + }, + { + .name = "YUV 4:2:2 semiplanar, YCbCr", + .fourcc = V4L2_PIX_FMT_NV16, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 16, + .h_align = 3, + .v_align = 3, + .types = JPU_ENC_OUTPUT, + }, + { + .name = "YUV 4:2:0 semiplanar, YCbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 12, + .h_align = 3, + .v_align = 3, + .types = JPU_ENC_OUTPUT, + }, + { + .name = "YUV 4:2:2 semiplanar, YCbCr", + .fourcc = V4L2_PIX_FMT_NV16, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 16, + .h_align = 2, + .v_align = 2, + .types = JPU_DEC_CAPTURE, + }, + { + .name = "YUV 4:2:0 semiplanar, YCbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = 12, + .h_align = 2, + .v_align = 2, + .types = 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 +}; + +static unsigned char hdr_blob0[] = { + 0xff, 0xd8, 0xff, 0xfe, 0x00, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x84 +}; + +static unsigned char hdr_blob1[] = { + 0xff, 0xc0, 0x00, 0x11, 0x08 +}; + +static unsigned char hdr_blob2[] = { + 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, + 0xc4, 0x01, 0xa2 +}; + +static const unsigned int qtbl_lum[2][16] = { + { + 0x14401927, 0x322e3e44, 0x10121726, 0x26354144, + 0x19171f26, 0x35414444, 0x27262635, 0x41444444, + 0x32263541, 0x44444444, 0x2e354144, 0x44444444, + 0x3e414444, 0x44444444, 0x44444444, 0x44444444 + }, + { + 0x08060608, 0x0c0e1011, 0x06060608, 0x0a0d0c0f, + 0x06060708, 0x0d0e1218, 0x0808080e, 0x0d131823, + 0x0c0a0d0d, 0x141a2227, 0x0e0d0e13, 0x1a222727, + 0x100c1318, 0x22272727, 0x110f1823, 0x27272727 + } +}; + +static const unsigned int qtbl_chr[2][16] = { + { + 0x15192026, 0x36444444, 0x191c1826, 0x36444444, + 0x2018202b, 0x42444444, 0x26262b35, 0x44444444, + 0x36424444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444 + }, + { + 0x0908090b, 0x0e111318, 0x080a090b, 0x0e0d1116, + 0x09090d0e, 0x0d0f171a, 0x0b0b0e0e, 0x0f141a21, + 0x0e0e0d0f, 0x14182127, 0x110d0f14, 0x18202727, + 0x1311171a, 0x21272727, 0x18161a21, 0x27272727 + } +}; + +static const unsigned int hdctbl_lum[7] = { + 0x00010501, 0x01010101, 0x01000000, 0x00000000, + 0x00010203, 0x04050607, 0x08090a0b +}; + +static const unsigned int hdctbl_chr[7] = { + 0x00030101, 0x01010101, 0x01010100, 0x00000000, + 0x00010203, 0x04050607, 0x08090a0b +}; + +static const unsigned int hactbl_lum[45] = { + 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[45] = { + 0x00020102, 0x04040304, 0x07050404, 0x00010277, + 0x00010203, 0x11040521, 0x31061241, 0x51076171, + 0x13223281, 0x08144291, 0xa1b1c109, 0x233352f0, + 0x156372d1, 0x0a162434, 0xe125f117, 0x18191a26, + 0x2728292a, 0x35363738, 0x393a4344, 0x45464748, + 0x494a5354, 0x55565758, 0x595a6364, 0x65666768, + 0x696a7374, 0x75767778, 0x797a8283, 0x84858687, + 0x88898a92, 0x93949596, 0x9798999a, 0xa2a3a4a5, + 0xa6a7a8a9, 0xaab2b3b4, 0xb5b6b7b8, 0xb9bac2c3, + 0xc4c5c6c7, 0xc8c9cad2, 0xd3d4d5d6, 0xd7d8d9da, + 0xe2e3e4e5, 0xe6e7e8e9, 0xeaf2f3f4, 0xf5f6f7f8, + 0xf9fa0000 +}; + +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(void __iomem *regs, const unsigned int *tbl, + int len) { + unsigned int i; + + for (i = 0; i < len; i++) + iowrite32(tbl[i], regs + (i << 2)); +} + +static void jpu_set_qtbl(void __iomem *regs, int quality) +{ + jpu_set_tbl(regs + JCQTBL(0), qtbl_lum[quality], + ARRAY_SIZE(qtbl_lum[quality])); + jpu_set_tbl(regs + JCQTBL(1), qtbl_chr[quality], + ARRAY_SIZE(qtbl_chr[quality])); +} + +static void jpu_set_htbl(void __iomem *regs) +{ + jpu_set_tbl(regs + JCHTBD(0), hdctbl_lum, ARRAY_SIZE(hdctbl_lum)); + jpu_set_tbl(regs + JCHTBD(1), hdctbl_chr, ARRAY_SIZE(hdctbl_chr)); + jpu_set_tbl(regs + JCHTBA(0), hactbl_lum, ARRAY_SIZE(hactbl_lum)); + jpu_set_tbl(regs + JCHTBA(1), hactbl_chr, ARRAY_SIZE(hactbl_chr)); +} + +static void jpu_int_clear(void __iomem *regs, unsigned int int_status) +{ + iowrite32(~int_status & JINTS_MASK, regs + JINTS); + + if (int_status & (INT(6) | INT(5) | INT(3))) + iowrite32(JCCMD_JEND, regs + JCCMD); +} + +static void jpu_reset(void __iomem *regs) +{ + iowrite32(JCCMD_SRST, regs + JCCMD); + while ((ioread32(regs + JCCMD) & JCCMD_SRST) != 0) + cpu_relax(); +} + +/* + * ============================================================================ + * video ioctl operations + * ============================================================================ + */ +static void put_byte(unsigned long *p, u8 v) +{ + u8 *addr = (u8 *)*p; + + *addr = v; + (*p)++; +} + +static void put_short_be(unsigned long *p, u16 v) +{ + u16 *addr = (u16 *)*p; + + *addr = cpu_to_be16(v); + *p += 2; +} + +static void put_word_be(unsigned long *p, u32 v) +{ + u32 *addr = (u32 *)*p; + + *addr = cpu_to_be32(v); + *p += 4; +} + +static void put_blob_byte(unsigned long *p, const unsigned char *blob, + unsigned int len) +{ + int i; + + for (i = 0; i < len; i++) + put_byte(p, blob[i]); +} + +static void put_qtbl(unsigned long *p, unsigned char id, + const unsigned int *qtbl) +{ + int i; + + put_byte(p, id); + for (i = 0; i < ARRAY_SIZE(zigzag); i++) + put_byte(p, *((u8 *)qtbl + zigzag[i])); +} + +static void put_htbl(unsigned long *p, unsigned char tc, + const unsigned int *htbl, unsigned int len) +{ + int i; + + put_byte(p, tc); + for (i = 0; i < len; i++) + put_word_be(p, htbl[i]); +} + +static void jpu_generate_hdr(struct jpu_q_data *q, int quality, void *buffer) +{ + /* SOI(2) - DQT(134 / 2 tables) - SOF0(19) - DHT(420 / 2 tables) */ + unsigned long p = (unsigned long)buffer; + + put_blob_byte(&p, hdr_blob0, ARRAY_SIZE(hdr_blob0)); + + put_qtbl(&p, 0x00, qtbl_lum[quality]); + put_qtbl(&p, 0x01, qtbl_chr[quality]); + + put_blob_byte(&p, hdr_blob1, ARRAY_SIZE(hdr_blob1)); + + put_short_be(&p, q->h); + put_short_be(&p, q->w); + + put_byte(&p, 0x03); + put_byte(&p, 0x01); + + if (q->fmt->fourcc == V4L2_PIX_FMT_NV16) + put_byte(&p, 0x21); + else + put_byte(&p, 0x22); + + put_blob_byte(&p, hdr_blob2, ARRAY_SIZE(hdr_blob2)); + + put_htbl(&p, 0x00, hdctbl_lum, ARRAY_SIZE(hdctbl_lum)); + put_htbl(&p, 0x10, hactbl_lum, ARRAY_SIZE(hactbl_lum)); + p -= 2; + + put_htbl(&p, 0x01, hdctbl_chr, ARRAY_SIZE(hdctbl_chr)); + put_htbl(&p, 0x11, hactbl_chr, ARRAY_SIZE(hactbl_chr)); +} + +static int jpu_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (ctx->mode == JPU_ENCODE) { + strlcpy(cap->driver, JPU_M2M_NAME " encoder", + sizeof(cap->driver)); + strlcpy(cap->card, JPU_M2M_NAME " encoder", + sizeof(cap->card)); + } else { + strlcpy(cap->driver, JPU_M2M_NAME " decoder", + sizeof(cap->driver)); + strlcpy(cap->card, JPU_M2M_NAME " decoder", + sizeof(cap->card)); + } + 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; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static struct jpu_fmt *jpu_find_format(unsigned int mode, u32 pixelformat, + unsigned int fmt_type) +{ + unsigned int k, fmt_flag; + + if (mode == JPU_ENCODE) + 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 (k = 0; k < ARRAY_SIZE(jpu_formats); k++) { + struct jpu_fmt *fmt = &jpu_formats[k]; + + if (fmt->fourcc == pixelformat && fmt->types & fmt_flag) + return fmt; + } + + return NULL; +} + +static int jpu_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num = 0; + + for (i = 0; i < ARRAY_SIZE(jpu_formats); ++i) { + if (jpu_formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* + * Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + /* Format not found */ + if (i >= ARRAY_SIZE(jpu_formats)) + return -EINVAL; + + strlcpy(f->description, jpu_formats[i].name, sizeof(f->description)); + f->pixelformat = jpu_formats[i].fourcc; + + return 0; +} + +static int jpu_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (ctx->mode == JPU_ENCODE) + return jpu_enum_fmt(f, JPU_ENC_CAPTURE); + + return jpu_enum_fmt(f, JPU_DEC_CAPTURE); +} + +static int jpu_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (ctx->mode == JPU_ENCODE) + return jpu_enum_fmt(f, JPU_ENC_OUTPUT); + + return jpu_enum_fmt(f, JPU_DEC_OUTPUT); +} + +static struct jpu_q_data *jpu_get_q_data(struct jpu_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &ctx->out_q; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &ctx->cap_q; + default: + return NULL; + } +} + +static int jpu_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct jpu_q_data *q_data; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct jpu_ctx *ctx = fh_to_ctx(priv); + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + ctx->mode == JPU_DECODE && !ctx->hdr_parsed) + return -EINVAL; + + q_data = jpu_get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + pix->width = q_data->w; + pix->height = q_data->h; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = q_data->fmt->fourcc; + pix->colorspace = q_data->fmt->colorspace; + pix->bytesperline = 0; + if (pix->pixelformat != V4L2_PIX_FMT_JPEG) + pix->bytesperline = q_data->w; + pix->sizeimage = q_data->size; + + return 0; +} + + +static void jpu_bound_align_image(u32 *w, unsigned int wmin, unsigned int wmax, + unsigned int walign, u32 *h, + unsigned int hmin, unsigned int hmax, + unsigned int halign) +{ + int width, height, w_step, h_step; + + width = *w; + height = *h; + + w_step = 1 << walign; + h_step = 1 << halign; + v4l_bound_align_image(w, wmin, wmax, walign, h, hmin, hmax, halign, 3); + + if (*w < width && *w + w_step < wmax) + *w += w_step; + if (*h < height && *h + h_step < hmax) + *h += h_step; +} + + +static int jpu_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format *pix = &f->fmt.pix; + struct jpu_fmt *fmt; + struct jpu_q_data *q_data; + unsigned int f_type; + + q_data = jpu_get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + f_type = V4L2_TYPE_IS_OUTPUT(f->type) ? JPU_FMT_TYPE_OUTPUT : + JPU_FMT_TYPE_CAPTURE; + + fmt = jpu_find_format(ctx->mode, pix->pixelformat, f_type); + if (!fmt) + fmt = q_data->default_fmt; + + pix->pixelformat = fmt->fourcc; + 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); + + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) { + if (pix->sizeimage <= 0) + pix->sizeimage = PAGE_SIZE; + pix->bytesperline = 0; + } else { + if (pix->bytesperline < pix->width) + pix->bytesperline = pix->width; + + pix->sizeimage = (pix->width * pix->height * fmt->depth) >> 3; + } + + pix->field = V4L2_FIELD_NONE; + pix->colorspace = fmt->colorspace; + + return 0; +} + +static int jpu_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format *pix = &f->fmt.pix; + struct vb2_queue *vq; + struct jpu_q_data *q_data; + unsigned int f_type; + int ret; + + ret = jpu_try_fmt(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = jpu_get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->jpu->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + f_type = V4L2_TYPE_IS_OUTPUT(f->type) ? JPU_FMT_TYPE_OUTPUT : + JPU_FMT_TYPE_CAPTURE; + + q_data->fmt = jpu_find_format(ctx->mode, pix->pixelformat, f_type); + q_data->w = pix->width; + q_data->h = pix->height; + if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) + q_data->size = q_data->w * q_data->h * q_data->fmt->depth >> 3; + else + q_data->size = pix->sizeimage; + + return 0; +} + +static int jpu_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + return -ENOTTY; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r.width = ctx->out_q.w; + s->r.height = ctx->out_q.h; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + s->r.width = ctx->cap_q.w; + s->r.height = ctx->cap_q.h; + break; + default: + return -EINVAL; + } + s->r.left = 0; + s->r.top = 0; + 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->slock, flags); + + if (ctrl->id == V4L2_CID_JPEG_COMPRESSION_QUALITY) + ctx->compr_quality = ctrl->val; + + spin_unlock_irqrestore(&ctx->jpu->slock, flags); + return 0; +} + +static int jpu_g_priority(struct file *file, void *priv, enum v4l2_priority *p) +{ + return -ENOTTY; +} + +static int jpu_s_priority(struct file *file, void *priv, enum v4l2_priority p) +{ + return -ENOTTY; +} + +static const struct v4l2_ctrl_ops jpu_ctrl_ops = { + .s_ctrl = jpu_s_ctrl, +}; + +static const struct v4l2_ioctl_ops jpu_ioctl_ops = { + .vidioc_querycap = jpu_querycap, + + .vidioc_enum_fmt_vid_cap = jpu_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = jpu_enum_fmt_vid_out, + + .vidioc_g_fmt_vid_cap = jpu_g_fmt, + .vidioc_g_fmt_vid_out = jpu_g_fmt, + + .vidioc_try_fmt_vid_cap = jpu_try_fmt, + .vidioc_try_fmt_vid_out = jpu_try_fmt, + + .vidioc_s_fmt_vid_cap = jpu_s_fmt, + .vidioc_s_fmt_vid_out = jpu_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = jpu_g_selection, + .vidioc_s_priority = jpu_s_priority, + .vidioc_g_priority = jpu_g_priority, + .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; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); + + if (ctx->mode == JPU_ENCODE) { + v4l2_ctrl_new_std(&ctx->ctrl_handler, &jpu_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, + 0, 1, 1, 1); + } + + if (ctx->ctrl_handler.error) + return ctx->ctrl_handler.error; + + if (ctx->mode == JPU_DECODE) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY; + + return 0; +} + +/* + * ============================================================================ + * 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 size, count = *nbuffers; + + q_data = jpu_get_q_data(ctx, vq->type); + if (q_data == NULL) + return -EINVAL; + + size = q_data->size; + + /* + * Header is parsed during decoding and parsed information stored + * in the context so we do not allow another buffer to overwrite it + */ + if (ctx->mode == JPU_DECODE) + count = 1; + + *nbuffers = count; + *nplanes = 1; + sizes[0] = size; + alloc_ctxs[0] = 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; + + q_data = jpu_get_q_data(ctx, vb->vb2_queue->type); + if (q_data == NULL) + return -EINVAL; + + if (vb2_plane_size(vb, 0) < q_data->size) { + pr_err("%s: data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), (long)q_data->size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->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->mode == JPU_DECODE && + vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + struct jpu_q_data *q_data; + struct jpu *jpu = ctx->jpu; + unsigned int subsampling, w_out, h_out, w_cap, h_cap; + unsigned long src_addr, flags; + + src_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + jpu_reset(jpu->regs); + + iowrite32(JCMOD_DSP_DEC | JCMOD_PCTR, jpu->regs + JCMOD); + iowrite32(JIFECNT_SWAP_WB, jpu->regs + JIFECNT); + iowrite32(JIFDCNT_SWAP_WB, jpu->regs + JIFDCNT); + + spin_lock_irqsave(&jpu->slock, flags); + jpu->status = JPU_BUSY; + spin_unlock_irqrestore(&ctx->jpu->slock, flags); + + iowrite32(INT(3), jpu->regs + JINTE); + iowrite32(src_addr, jpu->regs + JIFDSA1); + iowrite32(JCCMD_JSRT, jpu->regs + JCCMD); + + if (wait_event_interruptible_timeout(jpu->wq, + jpu->status != JPU_BUSY, + msecs_to_jiffies(100)) > 0 + && jpu->status == JPU_OK) + ctx->hdr_parsed = true; + + w_out = ioread32(jpu->regs + JCHSZU) << 8 | + ioread32(jpu->regs + JCHSZD); + + h_out = ioread32(jpu->regs + JCVSZU) << 8 | + ioread32(jpu->regs + JCVSZD); + + w_cap = ioread32(jpu->regs + JIFDDHSZ); + h_cap = ioread32(jpu->regs + JIFDDVSZ); + + switch (ioread32(jpu->regs + JCMOD) & JCMOD_REDU) { + case JCMOD_REDU_422: + subsampling = V4L2_PIX_FMT_NV16; + break; + case JCMOD_REDU_420: + subsampling = V4L2_PIX_FMT_NV12; + break; + default: + subsampling = 0; + } + + if (!ctx->hdr_parsed || !subsampling || w_out > JPU_WIDTH_MAX || + w_out < JPU_WIDTH_MIN || h_out > JPU_HEIGHT_MAX || + h_out < JPU_HEIGHT_MIN) { + pr_err("incompatible or corrupted JPEG data\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + return; + } + + q_data = &ctx->out_q; + q_data->w = w_out; + q_data->h = h_out; + + q_data = &ctx->cap_q; + q_data->w = w_cap; + q_data->h = h_cap; + + q_data->fmt = jpu_find_format(JPU_DECODE, subsampling, + JPU_FMT_TYPE_CAPTURE); + + q_data->size = + (q_data->w * q_data->h * q_data->fmt->depth) >> 3; + } + + if (ctx->fh.m2m_ctx) + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb); +} + +static int jpu_start_streaming(struct vb2_queue *q, unsigned int count) +{ + return 0; +} + +static void jpu_stop_streaming(struct vb2_queue *q) +{ +} + +static struct vb2_ops jpu_qops = { + .queue_setup = jpu_queue_setup, + .buf_prepare = jpu_buf_prepare, + .buf_queue = jpu_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = jpu_start_streaming, + .stop_streaming = jpu_stop_streaming, +}; + +static int jpu_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct jpu_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_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; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_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; + struct jpu_q_data *out_q, *cap_q; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&jpu->mutex)) { + ret = -ERESTARTSYS; + goto free; + } + + v4l2_fh_init(&ctx->fh, vfd); + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + out_q = &ctx->out_q; + cap_q = &ctx->cap_q; + + out_q->w = JPU_DEFAULT_WIDTH; + out_q->h = JPU_DEFAULT_HEIGHT; + + cap_q->w = JPU_DEFAULT_WIDTH; + cap_q->h = JPU_DEFAULT_HEIGHT; + + ctx->jpu = jpu; + if (vfd == jpu->vfd_encoder) { + ctx->mode = JPU_ENCODE; + out_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_NV16, + JPU_FMT_TYPE_OUTPUT); + cap_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_JPEG, + JPU_FMT_TYPE_CAPTURE); + out_q->size = (out_q->w * out_q->h * out_q->fmt->depth) >> 3; + cap_q->size = PAGE_SIZE; + } else { + ctx->mode = JPU_DECODE; + out_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_JPEG, + JPU_FMT_TYPE_OUTPUT); + cap_q->fmt = jpu_find_format(ctx->mode, V4L2_PIX_FMT_NV16, + JPU_FMT_TYPE_CAPTURE); + out_q->size = PAGE_SIZE; + cap_q->size = (cap_q->w * cap_q->h * cap_q->fmt->depth) >> 3; + } + + out_q->default_fmt = out_q->fmt; + cap_q->default_fmt = cap_q->fmt; + + 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 error; + } + + ret = jpu_controls_create(ctx); + if (ret < 0) + goto error; + + if (jpu->ref_count == 0) { + ret = clk_prepare_enable(jpu->clk); + if (ret < 0) + goto error; + } + + jpu->ref_count++; + + mutex_unlock(&jpu->mutex); + return 0; + +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&jpu->mutex); +free: + 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); + 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); + mutex_unlock(&jpu->mutex); + + 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_device_run(void *priv) +{ + struct jpu_ctx *ctx = priv; + struct jpu *jpu = ctx->jpu; + struct vb2_buffer *src_buf, *dst_buf; + unsigned long src_addr, dst_addr, flags; + void *dst_vaddr; + unsigned int w, h; + + spin_lock_irqsave(&ctx->jpu->slock, flags); + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); + dst_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + dst_vaddr = vb2_plane_vaddr(dst_buf, 0); + + jpu_reset(jpu->regs); + + if (ctx->mode == JPU_ENCODE) { + unsigned int redu, inft; + + w = ctx->out_q.w; + h = ctx->out_q.h; + + jpu_generate_hdr(&ctx->out_q, ctx->compr_quality, dst_vaddr); + + if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_NV12) { + redu = JCMOD_REDU_420; + inft = JIFECNT_INFT_420; + } else { + redu = JCMOD_REDU_422; + inft = JIFECNT_INFT_422; + } + + /* the only no marker mode works for encoding */ + iowrite32(JCMOD_DSP_ENC | JCMOD_PCTR | redu | JCMOD_SOI_ENABLE | + JCMOD_MSKIP_ENABLE, jpu->regs + JCMOD); + + iowrite32(JIFECNT_SWAP_WB | inft, jpu->regs + JIFECNT); + iowrite32(JIFDCNT_SWAP_WB, jpu->regs + JIFDCNT); + iowrite32(INT(10), jpu->regs + JINTE); + + /* Y and C components source addresses */ + iowrite32(src_addr, jpu->regs + JIFESYA1); + iowrite32(src_addr + w * h, jpu->regs + JIFESCA1); + + /* memory width */ + iowrite32(w, jpu->regs + JIFESMW); + + iowrite32((w >> 8) & JCSZ_MASK, jpu->regs + JCHSZU); + iowrite32(w & JCSZ_MASK, jpu->regs + JCHSZD); + + iowrite32((h >> 8) & JCSZ_MASK, jpu->regs + JCVSZU); + iowrite32(h & JCSZ_MASK, jpu->regs + JCVSZD); + + iowrite32(w, jpu->regs + JIFESHSZ); + iowrite32(h, jpu->regs + JIFESVSZ); + + iowrite32(dst_addr + JPU_JPEG_HDR_SIZE, jpu->regs + JIFEDA1); + + iowrite32(0 << JCQTN_SHIFT(1) | 1 << JCQTN_SHIFT(2) | + 1 << JCQTN_SHIFT(3), jpu->regs + JCQTN); + + iowrite32(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), + jpu->regs + JCHTN); + + jpu_set_qtbl(jpu->regs, ctx->compr_quality); + jpu_set_htbl(jpu->regs); + } else { + w = ctx->cap_q.w; + h = ctx->cap_q.h; + + iowrite32(JCMOD_DSP_DEC | JCMOD_PCTR, jpu->regs + JCMOD); + iowrite32(JIFECNT_SWAP_WB, jpu->regs + JIFECNT); + iowrite32(JIFDCNT_SWAP_WB, jpu->regs + JIFDCNT); + iowrite32(INT(10) | INT(7) | INT(6) | INT(5), + jpu->regs + JINTE); + iowrite32(src_addr, jpu->regs + JIFDSA1); + iowrite32(w, jpu->regs + JIFDDMW); + iowrite32(dst_addr, jpu->regs + JIFDDYA1); + iowrite32(dst_addr + w * h, jpu->regs + JIFDDCA1); + } + + iowrite32(JCCMD_JSRT, jpu->regs + JCCMD); + spin_unlock_irqrestore(&ctx->jpu->slock, flags); +} + +static int jpu_job_ready(void *priv) +{ + struct jpu_ctx *ctx = priv; + + if (ctx->mode == JPU_DECODE) + return ctx->hdr_parsed; + return 1; +} + +static void jpu_job_abort(void *priv) +{ +} + +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 long payload_size = 0; + unsigned int int_status; + unsigned int error; + + spin_lock(&jpu->slock); + + int_status = ioread32(jpu->regs + JINTS); + jpu_int_clear(jpu->regs, int_status); + + /* + * In any mode (decoding/encoding) we can additionaly get + * error status (5th bit) + * jpu operation complete status (6th bit) + */ + if (!((ioread32(jpu->regs + JINTE) | INT(5) | INT(6)) & int_status)) { + spin_unlock(&jpu->slock); + return IRQ_NONE; + } + + if (jpu->status == JPU_BUSY) { + if (int_status & INT(3)) + jpu->status = JPU_OK; + if (int_status & INT(5)) + jpu->status = JPU_ERR; + wake_up_interruptible(&jpu->wq); + goto handled; + } + + if ((int_status & INT(6)) && !(int_status & INT(10))) + goto handled; + + curr_ctx = v4l2_m2m_get_curr_priv(jpu->m2m_dev); + + 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 & INT(10)) { + if (curr_ctx->mode == JPU_ENCODE) { + payload_size = ioread32(jpu->regs + JCDTCU) << 16 | + ioread32(jpu->regs + JCDTCM) << 8 | + ioread32(jpu->regs + JCDTCD); + vb2_set_plane_payload(dst_buf, 0, + payload_size + JPU_JPEG_HDR_SIZE); + } + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + } else if (int_status & INT(5)) { + error = ioread32(jpu->regs + JCDERR); + dev_err(jpu->dev, "error 0x%X\n", error); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + } + + v4l2_m2m_job_finish(jpu->m2m_dev, curr_ctx->fh.m2m_ctx); + +handled: + spin_unlock(&jpu->slock); + return IRQ_HANDLED; +} + +/* + * ============================================================================ + * Driver basic infrastructure + * ============================================================================ + */ +static const struct of_device_id jpu_dt_ids[] = { + { .compatible = "renesas,jpu-r8a7790" }, + { .compatible = "renesas,jpu-r8a7791" }, + { .compatible = "renesas,jpu-gen2" }, + { }, +}; +MODULE_DEVICE_TABLE(of, jpu_dt_ids); + +static int jpu_probe(struct platform_device *pdev) +{ + struct jpu *jpu; + struct resource *res; + int ret; + + jpu = devm_kzalloc(&pdev->dev, sizeof(struct jpu), GFP_KERNEL); + if (!jpu) + return -ENOMEM; + + mutex_init(&jpu->mutex); + spin_lock_init(&jpu->slock); + 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"); + ret = PTR_ERR(jpu->clk); + return ret; + } + + /* v4l2 device */ + ret = v4l2_device_register(&pdev->dev, &jpu->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + goto clk_get_rollback; + } + + /* 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; + } + + /* JPEG encoder /dev/videoX node */ + jpu->vfd_encoder = video_device_alloc(); + if (!jpu->vfd_encoder) { + v4l2_err(&jpu->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto vb2_allocator_rollback; + } + strlcpy(jpu->vfd_encoder->name, JPU_M2M_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; + 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 enc_vdev_alloc_rollback; + } + + video_set_drvdata(jpu->vfd_encoder, jpu); + v4l2_info(&jpu->v4l2_dev, "encoder device registered as /dev/video%d\n", + jpu->vfd_encoder->num); + + /* JPEG decoder /dev/videoX node */ + jpu->vfd_decoder = video_device_alloc(); + if (!jpu->vfd_decoder) { + v4l2_err(&jpu->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto enc_vdev_register_rollback; + } + strlcpy(jpu->vfd_decoder->name, JPU_M2M_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; + 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 dec_vdev_alloc_rollback; + } + + video_set_drvdata(jpu->vfd_decoder, jpu); + v4l2_info(&jpu->v4l2_dev, "decoder device registered as /dev/video%d\n", + jpu->vfd_decoder->num); + + /* final statements & power management */ + platform_set_drvdata(pdev, jpu); + + init_waitqueue_head(&jpu->wq); + + v4l2_info(&jpu->v4l2_dev, "Renesas JPEG codec\n"); + + return 0; + +dec_vdev_alloc_rollback: + video_device_release(jpu->vfd_decoder); + +enc_vdev_register_rollback: + video_unregister_device(jpu->vfd_encoder); + +enc_vdev_alloc_rollback: + video_device_release(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); + +clk_get_rollback: + clk_disable_unprepare(jpu->clk); + + return ret; +} + +static int jpu_remove(struct platform_device *pdev) +{ + struct jpu *jpu = platform_get_drvdata(pdev); + + video_unregister_device(jpu->vfd_decoder); + video_device_release(jpu->vfd_decoder); + video_unregister_device(jpu->vfd_encoder); + video_device_release(jpu->vfd_encoder); + vb2_dma_contig_cleanup_ctx(jpu->alloc_ctx); + v4l2_m2m_release(jpu->m2m_dev); + v4l2_device_unregister(&jpu->v4l2_dev); + clk_disable_unprepare(jpu->clk); + + 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) + 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, + .owner = THIS_MODULE, + .name = JPU_M2M_NAME, + .pm = &jpu_pm_ops, + }, +}; + +module_platform_driver(jpu_driver); + +MODULE_ALIAS("jpu"); +MODULE_AUTHOR("Mikhail Ulianov <mikhail.ulyanov@cogentembedded.com>"); +MODULE_DESCRIPTION("Renesas JPEG codec driver"); +MODULE_LICENSE("GPL v2");
This patch contains driver for Renesas R-Car JPEG codec. Cnanges since v1: - s/g_fmt function simplified - default format for queues added - dumb vidioc functions added to be in compliance with standard api: jpu_s_priority, jpu_g_priority - standard v4l2_ctrl_subscribe_event and v4l2_event_unsubscribe now in use by the same reason Signed-off-by: Mikhail Ulyanov <mikhail.ulyanov@cogentembedded.com> --- drivers/media/platform/Kconfig | 11 + drivers/media/platform/Makefile | 2 + drivers/media/platform/jpu.c | 1628 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1641 insertions(+) create mode 100644 drivers/media/platform/jpu.c