* Re: [PATCH v4 03/11] dmaengine: Introduce min burst length capability
From: Andy Shevchenko @ 2020-05-29 12:07 UTC (permalink / raw)
To: Serge Semin
Cc: Vinod Koul, Viresh Kumar, Dan Williams, Serge Semin,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, Rob Herring,
linux-mips, devicetree, dmaengine, linux-kernel
In-Reply-To: <20200528222401.26941-4-Sergey.Semin@baikalelectronics.ru>
On Fri, May 29, 2020 at 01:23:53AM +0300, Serge Semin wrote:
> Some hardware aside from default 0/1 may have greater minimum burst
> transactions length constraints. Here we introduce the DMA device
> and slave capability, which if required can be initialized by the DMA
> engine driver with the device-specific value.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
>
> ---
>
> Changelog v3:
> - This is a new patch created as a result of the discussion with Vinud and
> Andy in the framework of DW DMA burst and LLP capabilities.
> ---
> drivers/dma/dmaengine.c | 1 +
> include/linux/dmaengine.h | 4 ++++
> 2 files changed, 5 insertions(+)
>
> diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
> index d31076d9ef25..b332ffe52780 100644
> --- a/drivers/dma/dmaengine.c
> +++ b/drivers/dma/dmaengine.c
> @@ -590,6 +590,7 @@ int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps)
> caps->src_addr_widths = device->src_addr_widths;
> caps->dst_addr_widths = device->dst_addr_widths;
> caps->directions = device->directions;
> + caps->min_burst = device->min_burst;
> caps->max_burst = device->max_burst;
> caps->residue_granularity = device->residue_granularity;
> caps->descriptor_reuse = device->descriptor_reuse;
> diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
> index e1c03339918f..0c7403b27133 100644
> --- a/include/linux/dmaengine.h
> +++ b/include/linux/dmaengine.h
> @@ -465,6 +465,7 @@ enum dma_residue_granularity {
> * Since the enum dma_transfer_direction is not defined as bit flag for
> * each type, the dma controller should set BIT(<TYPE>) and same
> * should be checked by controller as well
> + * @min_burst: min burst capability per-transfer
> * @max_burst: max burst capability per-transfer
> * @cmd_pause: true, if pause is supported (i.e. for reading residue or
> * for resume later)
> @@ -478,6 +479,7 @@ struct dma_slave_caps {
> u32 src_addr_widths;
> u32 dst_addr_widths;
> u32 directions;
> + u32 min_burst;
> u32 max_burst;
> bool cmd_pause;
> bool cmd_resume;
> @@ -769,6 +771,7 @@ struct dma_filter {
> * Since the enum dma_transfer_direction is not defined as bit flag for
> * each type, the dma controller should set BIT(<TYPE>) and same
> * should be checked by controller as well
> + * @min_burst: min burst capability per-transfer
> * @max_burst: max burst capability per-transfer
> * @residue_granularity: granularity of the transfer residue reported
> * by tx_status
> @@ -839,6 +842,7 @@ struct dma_device {
> u32 src_addr_widths;
> u32 dst_addr_widths;
> u32 directions;
> + u32 min_burst;
> u32 max_burst;
> bool descriptor_reuse;
> enum dma_residue_granularity residue_granularity;
> --
> 2.26.2
>
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v4 05/11] dmaengine: Introduce DMA-device device_caps callback
From: Andy Shevchenko @ 2020-05-29 12:12 UTC (permalink / raw)
To: Serge Semin
Cc: Vinod Koul, Viresh Kumar, Dan Williams, Serge Semin,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, Rob Herring,
linux-mips, devicetree, dmaengine, linux-kernel
In-Reply-To: <20200528222401.26941-6-Sergey.Semin@baikalelectronics.ru>
On Fri, May 29, 2020 at 01:23:55AM +0300, Serge Semin wrote:
> There are DMA devices (like ours version of Synopsys DW DMAC) which have
> DMA capabilities non-uniformly redistributed amongst the device channels.
> In order to provide a way of exposing the channel-specific parameters to
> the DMA engine consumers, we introduce a new DMA-device callback. In case
> if provided it gets called from the dma_get_slave_caps() method and is
> able to override the generic DMA-device capabilities.
I thought there is a pattern to return something, but it seems none.
So, I have nothing against it to return void.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
But consider one comment below.
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
>
> ---
>
> Changelog v3:
> - This is a new patch created as a result of the discussion with Vinod and
> Andy in the framework of DW DMA burst and LLP capabilities.
> ---
> drivers/dma/dmaengine.c | 3 +++
> include/linux/dmaengine.h | 2 ++
> 2 files changed, 5 insertions(+)
>
> diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
> index ad56ad58932c..edbb11d56cde 100644
> --- a/drivers/dma/dmaengine.c
> +++ b/drivers/dma/dmaengine.c
> @@ -599,6 +599,9 @@ int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps)
> caps->cmd_resume = !!device->device_resume;
> caps->cmd_terminate = !!device->device_terminate_all;
>
Perhaps a comment to explain that this is channel specific correction /
override / you name it on top of device level capabilities?
> + if (device->device_caps)
> + device->device_caps(chan, caps);
> +
> return 0;
> }
> EXPORT_SYMBOL_GPL(dma_get_slave_caps);
> diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
> index a7e4d8dfdd19..b303e59929e5 100644
> --- a/include/linux/dmaengine.h
> +++ b/include/linux/dmaengine.h
> @@ -899,6 +899,8 @@ struct dma_device {
> struct dma_chan *chan, dma_addr_t dst, u64 data,
> unsigned long flags);
>
> + void (*device_caps)(struct dma_chan *chan,
> + struct dma_slave_caps *caps);
> int (*device_config)(struct dma_chan *chan,
> struct dma_slave_config *config);
> int (*device_pause)(struct dma_chan *chan);
> --
> 2.26.2
>
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v4 07/11] dmaengine: dw: Set DMA device max segment size parameter
From: Andy Shevchenko @ 2020-05-29 12:18 UTC (permalink / raw)
To: Serge Semin
Cc: Vinod Koul, Viresh Kumar, Dan Williams, Serge Semin,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, Rob Herring,
linux-mips, devicetree, dmaengine, linux-kernel
In-Reply-To: <20200528222401.26941-8-Sergey.Semin@baikalelectronics.ru>
On Fri, May 29, 2020 at 01:23:57AM +0300, Serge Semin wrote:
> Maximum block size DW DMAC configuration corresponds to the max segment
> size DMA parameter in the DMA core subsystem notation. Lets set it with a
> value specific to the probed DW DMA controller. It shall help the DMA
> clients to create size-optimized SG-list items for the controller. This in
> turn will cause less dw_desc allocations, less LLP reinitializations,
> better DMA device performance.
Yes, something like that for time being, thanks!
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
>
> ---
>
> Changelog v2:
> - This is a new patch created in place of the dropped one:
> "dmaengine: dw: Add LLP and block size config accessors".
>
> Changelog v3:
> - Use the block_size found for the very first channel instead of looking for
> the maximum of maximum block sizes.
> - Don't define device-specific device_dma_parameters object, since it has
> already been defined by the platform device core.
> ---
> drivers/dma/dw/core.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c
> index 33e99d95b3d3..fb95920c429e 100644
> --- a/drivers/dma/dw/core.c
> +++ b/drivers/dma/dw/core.c
> @@ -1229,6 +1229,13 @@ int do_dma_probe(struct dw_dma_chip *chip)
> BIT(DMA_MEM_TO_MEM);
> dw->dma.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
>
> + /*
> + * For now there is no hardware with non uniform maximum block size
> + * across all of the device channels, so we set the maximum segment
> + * size as the block size found for the very first channel.
> + */
> + dma_set_max_seg_size(dw->dma.dev, dw->chan[0].block_size);
> +
> err = dma_async_device_register(&dw->dma);
> if (err)
> goto err_dma_register;
> --
> 2.26.2
>
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v4 08/11] dmaengine: dw: Add dummy device_caps callback
From: Andy Shevchenko @ 2020-05-29 12:19 UTC (permalink / raw)
To: Serge Semin
Cc: Vinod Koul, Viresh Kumar, Dan Williams, Serge Semin,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, Rob Herring,
linux-mips, devicetree, dmaengine, linux-kernel
In-Reply-To: <20200528222401.26941-9-Sergey.Semin@baikalelectronics.ru>
On Fri, May 29, 2020 at 01:23:58AM +0300, Serge Semin wrote:
> Since some DW DMA controllers (like one installed on Baikal-T1 SoC) may
> have non-uniform DMA capabilities per device channels, let's add
> the DW DMA specific device_caps callback to expose that specifics up to
> the DMA consumer. It's a dummy function for now. We'll fill it in with
> capabilities overrides in the next commits.
This one I leave to Vinod to decide what to do.
It is not harmful per se, but I consider better if it has a user already.
Thus, no tag, sorry.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [RFC PATCH V4 4/4] platform: mtk-isp: Add Mediatek FD driver
From: Jerry-ch Chen @ 2020-05-29 12:26 UTC (permalink / raw)
To: Tomasz Figa
Cc: Hans Verkuil, Hans Verkuil, Laurent Pinchart, Matthias Brugger,
Mauro Carvalho Chehab, Pi-Hsun Shih, yuzhao, zwisler,
moderated list:ARM/Mediatek SoC support,
list@263.net:IOMMU DRIVERS <iommu@lists.linux-foundation.org>, Joerg Roedel <joro@8bytes.org>,,
Sean Cheng (鄭昇弘), Sj Huang,
Christie Yu (游雅惠),
Frederic Chen (陳俊元),
Jungo Lin (林明俊), Linux Media Mailing List,
srv_heupstream, linux-devicetree, jerry-ch.chen
In-Reply-To: <CAAFQd5DodDfWsHkhQZP-M70k9_2sUwwb4zHtWfTx5EDyEKkwow@mail.gmail.com>
Hi Tomasz,
I Appreciate your review comments, here's the reply.
On Mon, 2020-05-25 at 14:24 +0200, Tomasz Figa wrote:
> r
>
> On Fri, May 22, 2020 at 4:11 PM Jerry-ch Chen
> <Jerry-ch.Chen@mediatek.com> wrote:
> >
> > Hi Tomasz,
> >
> > On Thu, 2020-05-21 at 18:28 +0000, Tomasz Figa wrote:
> > > Hi Jerry,
> > >
> > > On Wed, Dec 04, 2019 at 08:47:32PM +0800, Jerry-ch Chen wrote:
> [snip]
> > > > +
> > > > +enum face_angle {
> > > > + MTK_FD_FACE_FRONT,
> > > > + MTK_FD_FACE_RIGHT_50,
> > > > + MTK_FD_FACE_LEFT_50,
> > > > + MTK_FD_FACE_RIGHT_90,
> > > > + MTK_FD_FACE_LEFT_90,
> > > > + MTK_FD_FACE_ANGLE_NUM,
> > > > +};
> > >
> > > This enum seems to define values for the V4L2_CID_MTK_FD_DETECT_POSE
> > > control. Considering that this is an enumeration and the values are
> > > actually integers (-90, -50, 0, 50, 90), perhaps this should be an
> > > INTEGER_MENU control instead?
> > >
> >
> > this ioctl let user select multiple face positions(combination of angles
> > and directions) to be detected. so I thought I am not able to use the
> > INTEGER_MENU for this purpose.
>
> Ah, okay, I thought there is only one selection possible.
>
> >
> > A bit-field as following should be used by user.
> > I consider adding it to uapi.
> >
> > struct face_direction_def {
> > __u16 MTK_FD_FACE_DIR_0 : 1,
> > MTK_FD_FACE_DIR_30 : 1,
> > MTK_FD_FACE_DIR_60 : 1,
> > MTK_FD_FACE_DIR_90 : 1,
> > MTK_FD_FACE_DIR_120 : 1,
> > MTK_FD_FACE_DIR_150 : 1,
> > MTK_FD_FACE_DIR_180 : 1,
> > MTK_FD_FACE_DIR_210 : 1,
> > MTK_FD_FACE_DIR_240 : 1,
> > MTK_FD_FACE_DIR_270 : 1,
> > MTK_FD_FACE_DIR_300 : 1,
> > MTK_FD_FACE_DIR_330 : 1,
> > : 4;
> > };
>
> Note that bit fields are not recommended in UAPI because of not being
> well specified by the language. Instead bits should be defined using
> macros with explicit masks or sometimes enums.
>
Ok, I'll define them in macro with masks.
> >
> > User can also select some face directions of each face angle in one
> > ioctl, for example:
> >
> > /*
> > * u16 face_directions[MTK_FD_FACE_ANGLE_NUM] = {0};
> > *
> > * face_directions[MTK_FD_FACE_FRONT] = 0x7; //angle:0, dir:0,30,60
> > * face_directions[MTK_FACE_RIGHT_50] = 0x2; //angle:50, dir:30
> > *
> > */
>
> Makes sense, thanks.
>
> >
> > > > +
> > > > +struct fd_buffer {
> > > > + __u32 scp_addr; /* used by SCP */
> > > > + __u32 dma_addr; /* used by DMA HW */
> > > > +} __packed;
> > fd buffer is used for scp ipi
> >
> > > > +
> > > > +struct fd_face_result {
> > > > + char data[16];
> > > > +};
> > fd_face_result is used for user, so it should be moved to
> > include/uapi/linux.
> > In fact, it has bit-field definition for user, so I would like to define
> > it in include/uapi/linux as following:
> >
> > struct fd_face_result {
> > __u64 face_idx : 12,
> > type : 1,
> > x0 : 10,
> > y0 : 10,
> > x1 : 10,
> > y1 : 10,
> > fcv1 : 11;
> > __u64 fcv2 : 7,
> > rip_dir : 4,
> > rop_dir : 3,
> > det_size : 5;
> > };
> >
>
> Indeed this should be defined, but as per my comment above, please
> avoid using the bitfield construct and define shifts and masks
> instead.
>
Ok, I'll fix it.
> >
> > > > +
> > > > +struct fd_user_output {
> > > > + struct fd_face_result results[MTK_FD_MAX_RESULT_NUM];
> > > > + __u16 number;
> > >
> > > Is this perhaps the number of results? If so, would num_results be a better
> > > name?
> > >
> > yes, fixed.
> > > > +};
> > >
> > > Since this struct is the meta buffer format, it is a part of the userspace
> > > interface and should be defined in a header under include/uapi/linux/.
> > >
> > Ok, I will create include/uapi/linux/mtk_fd_40.h
> > which suppose to include structures that userspace will use.
> > should the private IOCTLs be placed in it together?
> >
>
> Sorry, what private IOCTLs are you referring to? If you mean private
> control IDs, then yes, they should go to that header.
yes, the IDs, sorry for the wrong expression.
>
> [snip]
> > > > +static int mtk_fd_vb2_queue_setup(struct vb2_queue *vq,
> > > > + unsigned int *num_buffers,
> > > > + unsigned int *num_planes,
> > > > + unsigned int sizes[],
> > > > + struct device *alloc_devs[])
> > > > +{
> > > > + struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
> > > > + unsigned int size[2];
> > > > + unsigned int plane;
> > > > +
> > > > + switch (vq->type) {
> > > > + case V4L2_BUF_TYPE_META_CAPTURE:
> > > > + size[0] = ctx->dst_fmt.buffersize;
> > > > + break;
> > > > + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> > > > + size[0] = ctx->src_fmt.plane_fmt[0].sizeimage;
> > > > + if (*num_planes == 2)
> > > > + size[1] = ctx->src_fmt.plane_fmt[1].sizeimage;
> > > > + break;
> > > > + }
> > >
> > > Is this code above needed? The code below sets sizes[] and it uses a for loop,
> > > without opencoded assignment for the second plane.
> > >
> >
> > Looks like not really useful here,
> > it should check sizes and num_planes if num_plane not zero,
> > and for V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, it will at most have 2
> > planes, maybe no need for loop as well.
>
> Loops generally make the code cleaner and there might be some desire
> to add support for more formats in the future, e.g. in case a next
> generation of the hardware shows up.
>
Ok, got it.
> > I will refine this function as following:
> > mtk_fd_vb2_queue_setup(...)
> > {
> > struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
> >
> > if (*num_planes == 0) {
> > if (vq->type == V4L2_BUF_TYPE_META_CAPTURE) {
> > sizes[0] = ctx->dst_fmt.buffersize;
> > *num_planes = 1;
> > return 0;
> > } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> > *num_planes = ctx->src_fmt.num_planes;
> > sizes[0] = ctx->src_fmt.plane_fmt[0].sizeimage;
> > if (*num_planes == 2)
> > sizes[1] = ctx->src_fmt.plane_fmt[1].sizeimage;
> > return 0;
> > }
> > return -EINVAL;
> > }
> >
> > /* If num_plane not zero, check the num_plane and sizes*/
> > if (vq->type == V4L2_BUF_TYPE_META_CAPTURE) {
> > if ((*num_planes == 1) &&
> > (sizes[0] <= ctx->dst_fmt.buffersize))
> > return 0;
>
> nit: The typical convention is to check for problems and return the
> error code earlier, with the success handled at the end of the block.
>
Got it.
> > else
> > return -EINVAL;
> > }
> > if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> > if ((*num_planes == 1) &&
> > (sizes[0] <= ctx->src_fmt.plane_fmt[0].sizeimage))
> > return 0;
> > else if ((*num_planes == 2) &&
> > (sizes[0] <= ctx->src_fmt.plane_fmt[0].sizeimage) &&
> > (sizes[1] <= ctx->src_fmt.plane_fmt[1].sizeimage))
> > return 0;
>
> Wouldn't a loop eliminate the need to if/else if through the various
> supported cases and duplicate the size checks?
>
> > else
> > return -EINVAL;
> >
> > }
> > return 0;
> > }
>
> How about the following?
>
> mtk_fd_vb2_queue_setup(...)
> {
> struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq);
>
> if (vq->type == V4L2_BUF_TYPE_META_CAPTURE) {
> if (*num_planes == 0) {
> *num_planes = 1;
> sizes[0] = ctx->dst_fmt.buffersize;
> return 0;
> }
>
> if (*num_planes != 1 || sizes[0] < ctx->dst_fmt.buffersize)
> return -EINVAL;
>
> return 0;
> }
>
> /* V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE */
> if (*num_planes == 0) {
> *num_planes = ctx->src_fmt.num_planes;
> for (i = 0; i < ctx->src_fmt.num_planes; ++i)
> sizes[i] = ctx->src_fmt.plane_fmt[i].sizeimage;
> return 0;
> }
>
> if (*num_planes < ctx->src_fmt.num_planes)
> return -EINVAL;
>
> for (i = 0; i < ctx->src_fmt.num_planes; ++i)
> if (sizes[i] < ctx->src_fmt.plane_fmt[i].sizeimage)
> return -EINVAL;
>
> return 0;
> }
>
> Note that it fully separates the code dealing with each queue and thus
> improves the readability.
>
> In this case, it could actually be beneficial to split the vb2_ops
> implementation into one that deals only with video_output_mplane and
> one only with meta_capture. This would allow eliminating the special
> casing based on vq->type and thus further simplify the code. Not sure
> if it applies to the other vb2 callbacks, though.
>
Got it, thanks for the comments.
> [snip]
> > > > +static void mtk_fd_fill_pixfmt_mp(struct v4l2_pix_format_mplane *dfmt,
> > > > + const struct v4l2_pix_format_mplane *sfmt)
> > > > +{
> > > > + dfmt->field = V4L2_FIELD_NONE;
> > > > + dfmt->colorspace = V4L2_COLORSPACE_BT2020;
> > > > + dfmt->num_planes = sfmt->num_planes;
> > > > + dfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> > > > + dfmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> > > > + dfmt->xfer_func =
> > > > + V4L2_MAP_XFER_FUNC_DEFAULT(dfmt->colorspace);
> > > > +
> > > > + /* Keep user setting as possible */
> > > > + dfmt->width = clamp(dfmt->width,
> > > > + MTK_FD_OUTPUT_MIN_WIDTH,
> > > > + MTK_FD_OUTPUT_MAX_WIDTH);
> > > > + dfmt->height = clamp(dfmt->height,
> > > > + MTK_FD_OUTPUT_MIN_HEIGHT,
> > > > + MTK_FD_OUTPUT_MAX_HEIGHT);
> > > > +
> > > > + if (sfmt->num_planes == 2) {
> > > > + /* NV16M and NV61M has 1 byte per pixel */
> > > > + dfmt->plane_fmt[0].bytesperline = dfmt->width;
> > > > + dfmt->plane_fmt[1].bytesperline = dfmt->width;
> > > > + } else {
> > > > + /* 2 bytes per pixel */
> > > > + dfmt->plane_fmt[0].bytesperline = dfmt->width * 2;
> > > > + }
> > > > +
> > > > + dfmt->plane_fmt[0].sizeimage =
> > > > + dfmt->height * dfmt->plane_fmt[0].bytesperline;
> > >
> > > Could some of the code above be replaced with v4l2_fill_pixfmt_mp()?
> > >
> > I would like to refine as following
> >
> > mtk_fd_fill_pixfmt_mp(...){
> > v4l2_fill_pixfmt_mp(dfmt, sfmt->pixelformat, dfmt->width,
> > dfmt->height);
> >
> > dfmt->field = V4L2_FIELD_NONE;
> > dfmt->colorspace = V4L2_COLORSPACE_BT2020;
> > dfmt->num_planes = sfmt->num_planes;
>
> num_planes should be already filled in by the helper. That actually
> raises a question on whether there is any need to have sfmt passed to
> this function at all. Perhaps all we need is the value of pixelformat?
>
Yes, I'll replace sfmt with u32 pixfmt.
> > dfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> > dfmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> > dfmt->xfer_func =
> > V4L2_MAP_XFER_FUNC_DEFAULT(dfmt->colorspace);
> > }
> >
>
> Isn't still a need to clamp() width and height to min/max, though?
Yes, I'll add them back.
This function will be refined as :
static void mtk_fd_fill_pixfmt_mp(struct v4l2_pix_format_mplane *dfmt,
u32 pixfmt)
{
v4l2_fill_pixfmt_mp(dfmt, pixfmt, dfmt->width, dfmt->height);
dfmt->field = V4L2_FIELD_NONE;
dfmt->colorspace = V4L2_COLORSPACE_BT2020;
dfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
dfmt->quantization = V4L2_QUANTIZATION_DEFAULT;
dfmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(dfmt->colorspace);
/* Keep user setting as possible */
dfmt->width = clamp(dfmt->width,
MTK_FD_OUTPUT_MIN_WIDTH,
MTK_FD_OUTPUT_MAX_WIDTH);
dfmt->height = clamp(dfmt->height,
MTK_FD_OUTPUT_MIN_HEIGHT,
MTK_FD_OUTPUT_MAX_HEIGHT);
}
>
> [snip]
> > > > + fd_param.user_param.src_img_fmt =
> > > > + get_fd_img_fmt(ctx->src_fmt.pixelformat);
> > > > + if (ctx->src_fmt.num_planes == 2)
> > > > + fd_param.src_img[1].dma_addr =
> > > > + vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 1);
> > >
> > > nit: Could this be moved above, to be just below src_img[0] initialization,
> > > for readability reasons?
> > >
> > Ok, this function will be refined as
> >
> > static void mtk_fd_device_run(void *priv)
> > {
> > struct mtk_fd_ctx *ctx = priv;
> > struct mtk_fd_dev *fd = ctx->fd_dev;
> > struct vb2_v4l2_buffer *src_buf, *dst_buf;
> > struct fd_enq_param fd_param;
> >
> > src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
> > dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
> >
> > fd_param.src_img[0].dma_addr =
> > vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
> > if (ctx->src_fmt.num_planes == 2)
> > fd_param.src_img[1].dma_addr =
> > vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 1);
>
> How about making this a loop in terms of ctx->src_fmt.num_planes?
>
yes, it will be refined as following, I use the src_vb2_buf to reduce
the length for fitting 80 columns
src_vb2_buf = &src_buf->vb2_buf;
dst_vb2_buf = &dst_buf->vb2_buf;
for (i = 0; i < ctx->src_fmt.num_planes; i++)
fd_param.src_img[i].dma_addr =
vb2_dma_contig_plane_dma_addr(src_vb2_buf,i);
fd_param.user_result.dma_addr =
vb2_dma_contig_plane_dma_addr(dst_vb2_buf, 0);
> > fd_param.user_result.dma_addr =
> > vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
> > fd_param.user_param.src_img_fmt =
> > get_fd_img_fmt(fd->dev, ctx->src_fmt.pixelformat);
> >
> > mtk_fd_fill_user_param(&fd_param.user_param, &ctx->hdl);
> >
> > /* Complete request controls if any */
> > v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req, &ctx->hdl);
> >
> > fd->output = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
> > mtk_fd_hw_job_exec(fd, &fd_param);
> > }
>
> Best regards,
> Tomasz
Thanks and Best regards,
Jerry
^ permalink raw reply
* Re: [PATCH 1/4] dt-bindings: thermal: rcar-thermal: Add device tree support for r8a7742
From: Geert Uytterhoeven @ 2020-05-29 12:48 UTC (permalink / raw)
To: Lad Prabhakar
Cc: Magnus Damm, Rob Herring, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Linux Kernel Mailing List, Prabhakar
In-Reply-To: <1590614320-30160-2-git-send-email-prabhakar.mahadev-lad.rj@bp.renesas.com>
On Wed, May 27, 2020 at 11:19 PM Lad Prabhakar
<prabhakar.mahadev-lad.rj@bp.renesas.com> wrote:
> Add thermal sensor support for r8a7742 SoC. The Renesas RZ/G1H
> (r8a7742) thermal sensor module is identical to the R-Car Gen2 family.
>
> No driver change is needed due to the fallback compatible value
> "renesas,rcar-gen2-thermal".
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> Reviewed-by: Marian-Cristian Rotariu <marian-cristian.rotariu.rb@bp.renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 3/4] dt-bindings: timer: renesas,cmt: Document r8a7742 CMT support
From: Geert Uytterhoeven @ 2020-05-29 12:53 UTC (permalink / raw)
To: Lad Prabhakar
Cc: Magnus Damm, Rob Herring, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Linux Kernel Mailing List, Prabhakar
In-Reply-To: <1590614320-30160-4-git-send-email-prabhakar.mahadev-lad.rj@bp.renesas.com>
Hi Prabhakar,
On Wed, May 27, 2020 at 11:19 PM Lad Prabhakar
<prabhakar.mahadev-lad.rj@bp.renesas.com> wrote:
> Document SoC specific compatible strings for r8a7742. No driver change
> is needed as the fallback strings will activate the right code.
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> Reviewed-by: Marian-Cristian Rotariu <marian-cristian.rotariu.rb@bp.renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Please note this DT binding is under yamlization, cfr.
"[PATCH v2] dt-bindings: timer: renesas: cmt: Convert to json-schema"
(20200505155127.4836-1-geert+renesas@glider.be).
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [RFC PATCH V4 4/4] platform: mtk-isp: Add Mediatek FD driver
From: Tomasz Figa @ 2020-05-29 12:59 UTC (permalink / raw)
To: Jerry-ch Chen
Cc: Hans Verkuil, Hans Verkuil, Laurent Pinchart, Matthias Brugger,
Mauro Carvalho Chehab, Pi-Hsun Shih, yuzhao, zwisler,
moderated list:ARM/Mediatek SoC support,
list@263.net:IOMMU DRIVERS <iommu@lists.linux-foundation.org>, Joerg Roedel <joro@8bytes.org>,,
Sean Cheng (鄭昇弘), Sj Huang,
Christie Yu (游雅惠),
Frederic Chen (陳俊元),
Jungo Lin (林明俊), Linux Media Mailing List,
srv_heupstream, linux-devicetree
In-Reply-To: <1590755163.23156.24.camel@mtksdccf07>
On Fri, May 29, 2020 at 2:26 PM Jerry-ch Chen
<Jerry-ch.Chen@mediatek.com> wrote:
>
> Hi Tomasz,
>
> I Appreciate your review comments, here's the reply.
>
> On Mon, 2020-05-25 at 14:24 +0200, Tomasz Figa wrote:
> > r
> >
> > On Fri, May 22, 2020 at 4:11 PM Jerry-ch Chen
> > <Jerry-ch.Chen@mediatek.com> wrote:
> > >
> > > Hi Tomasz,
> > >
> > > On Thu, 2020-05-21 at 18:28 +0000, Tomasz Figa wrote:
> > > > Hi Jerry,
> > > >
> > > > On Wed, Dec 04, 2019 at 08:47:32PM +0800, Jerry-ch Chen wrote:
[snip]
> > Isn't still a need to clamp() width and height to min/max, though?
> Yes, I'll add them back.
>
> This function will be refined as :
>
> static void mtk_fd_fill_pixfmt_mp(struct v4l2_pix_format_mplane *dfmt,
> u32 pixfmt)
> {
> v4l2_fill_pixfmt_mp(dfmt, pixfmt, dfmt->width, dfmt->height);
>
> dfmt->field = V4L2_FIELD_NONE;
> dfmt->colorspace = V4L2_COLORSPACE_BT2020;
> dfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> dfmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> dfmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(dfmt->colorspace);
>
> /* Keep user setting as possible */
> dfmt->width = clamp(dfmt->width,
> MTK_FD_OUTPUT_MIN_WIDTH,
> MTK_FD_OUTPUT_MAX_WIDTH);
> dfmt->height = clamp(dfmt->height,
> MTK_FD_OUTPUT_MIN_HEIGHT,
> MTK_FD_OUTPUT_MAX_HEIGHT);
Note that this would cause the other fields of dfmt to be inconsistent
with width and height. The correct way to do this would be to first
clamp and then call v4l2_fill_pixfmt_mp().
Best regards,
Tomasz
^ permalink raw reply
* Re: [PATCHv3 2/2] spi: dw: add optional reset property
From: Mark Brown @ 2020-05-29 13:02 UTC (permalink / raw)
To: Dinh Nguyen
Cc: linux-kernel, devicetree, robh+dt, linux-spi, Sergey.Semin,
fancer.lancer, andriy.shevchenko, lars.povlsen
In-Reply-To: <20200527204110.25676-2-dinguyen@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 159 bytes --]
On Wed, May 27, 2020 at 03:41:10PM -0500, Dinh Nguyen wrote:
> Add optional reset property.
This doesn't apply against current code, please check and resend.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 484 bytes --]
^ permalink raw reply
* [PATCH v2 0/3] media: rockchip: Introduce driver for the camera interface on PX30
From: Maxime Chevallier @ 2020-05-29 13:04 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Robin Murphy, Rob Herring, Mark Rutland,
Heiko Stuebner, Hans Verkuil
Cc: Maxime Chevallier, linux-media, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Thomas Petazzoni, Miquel Raynal,
Paul Kocialkowski
Hello everyone,
Here's a V2 of the series adding very basic support for the camera interface on
the Rockchip PX30 SoC.
Thanks to everyone that commented on the first series, your reviews were
very helpful :)
This Camera Interface is also supported on other Rockchip SoC such as
the RK1808, RK3128, RK3288 and RK3288, but for now I've only been able to
test it on the PX30, using a PAL format.
This driver is mostly based on the driver found in Rockchip's BSP, that
has been trimmed down to support the set of features that I was able to test,
that is pretty much a very basic one-frame capture and video streaming
with GStreamer.
This first draft only supports the Parallel interface, although the
controller has support for BT656 and CSI2.
Finally, this controller has an iommu that could be used in this driver,
but as of today I've not been able to get it to work.
Any review is welcome.
Thanks,
Maxime
--- Changes since V1 ---
- Took reviews from Rob, Hans, Robin and Heiko into account :
- Renamed the clocks in the binding
- Fixed the DT schema compiling
- Fixed a few typos
- Used the clk bulk API
- Used the reset array API
- Changed a few helpers for more suitable ones
- Rebased on 5.7-rc7
Maxime Chevallier (3):
media: dt-bindings: media: Document Rockchip CIF bindings
media: rockchip: Introduce driver for Rockhip's camera interface
arm64: dts: rockchip: Add the camera interface description of the PX30
.../bindings/media/rockchip-cif.yaml | 100 ++
arch/arm64/boot/dts/rockchip/px30.dtsi | 12 +
drivers/media/platform/Kconfig | 13 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/rockchip/cif/Makefile | 3 +
drivers/media/platform/rockchip/cif/capture.c | 1170 +++++++++++++++++
drivers/media/platform/rockchip/cif/dev.c | 358 +++++
drivers/media/platform/rockchip/cif/dev.h | 213 +++
drivers/media/platform/rockchip/cif/regs.h | 256 ++++
9 files changed, 2126 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/rockchip-cif.yaml
create mode 100644 drivers/media/platform/rockchip/cif/Makefile
create mode 100644 drivers/media/platform/rockchip/cif/capture.c
create mode 100644 drivers/media/platform/rockchip/cif/dev.c
create mode 100644 drivers/media/platform/rockchip/cif/dev.h
create mode 100644 drivers/media/platform/rockchip/cif/regs.h
--
2.25.4
^ permalink raw reply
* [PATCH v2 1/3] media: dt-bindings: media: Document Rockchip CIF bindings
From: Maxime Chevallier @ 2020-05-29 13:04 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Robin Murphy, Rob Herring, Mark Rutland,
Heiko Stuebner, Hans Verkuil
Cc: Maxime Chevallier, linux-media, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Thomas Petazzoni, Miquel Raynal,
Paul Kocialkowski
In-Reply-To: <20200529130405.929429-1-maxime.chevallier@bootlin.com>
Add a documentation for the Rockchip Camera Interface controller
binding.
This controller can be found on platforms such as the PX30 or the
RK3288, the PX30 being the only platform supported so far.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Changes since V1
- Updated the clock and reset names
- Added missing includes in the example, so that the make dt_binding_check passes
.../bindings/media/rockchip-cif.yaml | 100 ++++++++++++++++++
1 file changed, 100 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/rockchip-cif.yaml
diff --git a/Documentation/devicetree/bindings/media/rockchip-cif.yaml b/Documentation/devicetree/bindings/media/rockchip-cif.yaml
new file mode 100644
index 000000000000..f11a30ca9d42
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip-cif.yaml
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rockchip-cif.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip Camera Interface (CIF)
+
+maintainers:
+ - Maxime Chevallier <maxime.chevallier@bootlin.com>
+
+description: |-
+ Camera Interface for Rockcip platforms
+
+properties:
+ compatible:
+ const: rockchip,px30-cif
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: ACLK
+ - description: HCLK
+ - description: PCLK IN
+
+ clock-names:
+ items:
+ - const: aclk
+ - const: hclkf
+ - const: pclkin
+
+ resets:
+ items:
+ - description: AXI
+ - description: AHB
+ - description: PCLK IN
+
+ reset-names:
+ items:
+ - const: axi
+ - const: ahb
+ - const: pclkin
+
+ power-domains:
+ maxItems: 1
+ description: phandle to the associated power domain
+
+ # See ./video-interfaces.txt for details
+ port:
+ type: object
+ additionalProperties: false
+
+ properties:
+ endpoint:
+ type: object
+
+ properties:
+ remote-endpoint: true
+
+ required:
+ - remote-endpoint
+
+ required:
+ - endpoint
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/px30-cru.h>
+ #include <dt-bindings/power/px30-power.h>
+
+ cif: cif@ff490000 {
+ compatible = "rockchip,px30-cif";
+ reg = <0x0 0xff490000 0x0 0x200>;
+ interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_CIF>, <&cru HCLK_CIF>, <&cru PCLK_CIF>, <&cru SCLK_CIF_OUT>;
+ clock-names = "aclk_cif", "hclk_cif", "pclk_cif", "cif_out";
+ resets = <&cru SRST_CIF_A>, <&cru SRST_CIF_H>, <&cru SRST_CIF_PCLKIN>;
+ reset-names = "rst_cif_a", "rst_cif_h", "rst_cif_pclkin";
+ power-domains = <&power PX30_PD_VI>;
+ port {
+ cif_in: endpoint {
+ remote-endpoint = <&tw9900_out>;
+ };
+ };
+ };
+...
--
2.25.4
^ permalink raw reply related
* [PATCH v2 3/3] arm64: dts: rockchip: Add the camera interface description of the PX30
From: Maxime Chevallier @ 2020-05-29 13:04 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Robin Murphy, Rob Herring, Mark Rutland,
Heiko Stuebner, Hans Verkuil
Cc: Maxime Chevallier, linux-media, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Thomas Petazzoni, Miquel Raynal,
Paul Kocialkowski
In-Reply-To: <20200529130405.929429-1-maxime.chevallier@bootlin.com>
The PX30 has a camera interface, supporting CSI2, BT656 and Parallel
modes. Add a DT description for this interface.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Changes since V1:
- Updated the clock and reset names
- Reordered the properties to have clocks and resets bundled together
arch/arm64/boot/dts/rockchip/px30.dtsi | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/px30.dtsi b/arch/arm64/boot/dts/rockchip/px30.dtsi
index 9f84aaaf3fba..d895541b7216 100644
--- a/arch/arm64/boot/dts/rockchip/px30.dtsi
+++ b/arch/arm64/boot/dts/rockchip/px30.dtsi
@@ -1140,6 +1140,18 @@ vopl_mmu: iommu@ff470f00 {
status = "disabled";
};
+ cif: cif@ff490000 {
+ compatible = "rockchip,px30-cif";
+ reg = <0x0 0xff490000 0x0 0x200>;
+ interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_CIF>, <&cru HCLK_CIF>, <&cru PCLK_CIF>, <&cru SCLK_CIF_OUT>;
+ clock-names = "aclk", "hclk", "pclkin";
+ power-domains = <&power PX30_PD_VI>;
+ resets = <&cru SRST_CIF_A>, <&cru SRST_CIF_H>, <&cru SRST_CIF_PCLKIN>;
+ reset-names = "axi", "ahb", "pclkin";
+ status = "disabled";
+ };
+
qos_gmac: qos@ff518000 {
compatible = "syscon";
reg = <0x0 0xff518000 0x0 0x20>;
--
2.25.4
^ permalink raw reply related
* [PATCH v2 2/3] media: rockchip: Introduce driver for Rockhip's camera interface
From: Maxime Chevallier @ 2020-05-29 13:04 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Robin Murphy, Rob Herring, Mark Rutland,
Heiko Stuebner, Hans Verkuil
Cc: Maxime Chevallier, linux-media, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Thomas Petazzoni, Miquel Raynal,
Paul Kocialkowski
In-Reply-To: <20200529130405.929429-1-maxime.chevallier@bootlin.com>
Introduce a driver for the camera interface on some Rockchip platforms.
This controller supports CSI2, Parallel and BT656 interfaces, but for
now only the parallel interface could be tested, hence it's the only one
that's supported in the first version of this driver.
This controller can be fond on PX30, RK1808, RK3128, RK3288 and RK3288,
but for now it's only be tested on PX30.
Most of this driver was written follwing the BSP driver from rockchip,
removing the parts that either didn't fit correctly the guidelines, or
that couldn't be tested.
This basic version doesn't support cropping nor scaling, and is only
designed with one sensor being attached to it a any time.
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Changes since V1 :
- Convert to the bulk APIs for clocks and resets
- remove useless references to priv data
- Move around some init functions at probe time
- Upate some helpers to more suitable ones
Here is the output from v4l2-compliance. There are no fails in the final
summary, but there is one in the output that I didn't catch previously.
Still, here's the V2 in the meantime, if you have any further reviews
ompliance SHA: not available, 64 bits
Compliance test for rkcif device /dev/video0:
Driver Info:
Driver name : rkcif
Card type : rkcif
Bus info : platform:ff490000.cif
Driver version : 5.7.0
Capabilities : 0x84201000
Video Capture Multiplanar
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04201000
Video Capture Multiplanar
Streaming
Extended Pix Format
Media Driver Info:
Driver name : rkcif
Model : rkcif
Serial :
Bus info :
Media version : 5.7.0
Hardware revision: 0x00000000 (0)
Driver version : 5.7.0
Interface Info:
ID : 0x03000002
Type : V4L Video
Entity Info:
ID : 0x00000001 (1)
Name : video_rkcif
Function : V4L2 I/O
Pad 0x01000004 : 0: Sink
Link 0x02000007: from remote pad 0x1000006 of entity 'tw9900 2-0044': Data, Enabled
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
test VIDIOC_QUERYCAP: OK
Allow for multiple opens:
test second /dev/video0 open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls (Input 0):
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0
Format ioctls (Input 0):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
fail: v4l2-test-formats.cpp(1772): node->can_scale && node->frmsizes_count[v4l_format_g_pixelformat(&cur)]
test Scaling: OK
Codec ioctls (Input 0):
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls (Input 0):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test VIDIOC_EXPBUF: OK
test Requests: OK (Not Supported)
Total for rkcif device /dev/video0: 45, Succeeded: 45, Failed: 0, Warnings: 0
drivers/media/platform/Kconfig | 13 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/rockchip/cif/Makefile | 3 +
drivers/media/platform/rockchip/cif/capture.c | 1170 +++++++++++++++++
drivers/media/platform/rockchip/cif/dev.c | 358 +++++
drivers/media/platform/rockchip/cif/dev.h | 213 +++
drivers/media/platform/rockchip/cif/regs.h | 256 ++++
7 files changed, 2014 insertions(+)
create mode 100644 drivers/media/platform/rockchip/cif/Makefile
create mode 100644 drivers/media/platform/rockchip/cif/capture.c
create mode 100644 drivers/media/platform/rockchip/cif/dev.c
create mode 100644 drivers/media/platform/rockchip/cif/dev.h
create mode 100644 drivers/media/platform/rockchip/cif/regs.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index e01bbb9dd1c1..d4ec5e36bca7 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -460,6 +460,19 @@ config VIDEO_ROCKCHIP_RGA
To compile this driver as a module choose m here.
+config VIDEO_ROCKCHIP_CIF
+ tristate "Rockchip Camera Interface"
+ depends on VIDEO_DEV && VIDEO_V4L2
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ select VIDEOBUF2_DMA_SG
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_FWNODE
+ select V4L2_MEM2MEM_DEV
+ help
+ This is a v4l2 driver for Rockchip SOC Camera interface.
+
+ To compile this driver as a module choose m here.
+
config VIDEO_TI_VPE
tristate "TI VPE (Video Processing Engine) driver"
depends on VIDEO_DEV && VIDEO_V4L2
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index d13db96e3015..67e7ac034be1 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o
obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/
obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip/rga/
+obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip/cif/
obj-y += omap/
diff --git a/drivers/media/platform/rockchip/cif/Makefile b/drivers/media/platform/rockchip/cif/Makefile
new file mode 100644
index 000000000000..727990824316
--- /dev/null
+++ b/drivers/media/platform/rockchip/cif/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += video_rkcif.o
+video_rkcif-objs += dev.o capture.o
diff --git a/drivers/media/platform/rockchip/cif/capture.c b/drivers/media/platform/rockchip/cif/capture.c
new file mode 100644
index 000000000000..adab6704129f
--- /dev/null
+++ b/drivers/media/platform/rockchip/cif/capture.c
@@ -0,0 +1,1170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip CIF Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "dev.h"
+#include "regs.h"
+
+#define CIF_REQ_BUFS_MIN 1
+#define CIF_MIN_WIDTH 64
+#define CIF_MIN_HEIGHT 64
+#define CIF_MAX_WIDTH 8192
+#define CIF_MAX_HEIGHT 8192
+
+#define RKCIF_PLANE_Y 0
+#define RKCIF_PLANE_CBCR 1
+
+#define CIF_FETCH_Y_LAST_LINE(VAL) ((VAL) & 0x1fff)
+/* Check if swap y and c in bt1120 mode */
+#define CIF_FETCH_IS_Y_FIRST(VAL) ((VAL) & 0xf)
+
+/* Get xsubs and ysubs for fourcc formats
+ *
+ * @xsubs: horizontal color samples in a 4*4 matrix, for yuv
+ * @ysubs: vertical color samples in a 4*4 matrix, for yuv
+ */
+static int fcc_xysubs(u32 fcc, u32 *xsubs, u32 *ysubs)
+{
+ switch (fcc) {
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ *xsubs = 2;
+ *ysubs = 1;
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV12:
+ *xsubs = 2;
+ *ysubs = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct cif_output_fmt out_fmts[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_NV16,
+ .cplanes = 2,
+ .mplanes = 1,
+ .fmt_val = YUV_OUTPUT_422 | UV_STORAGE_ORDER_UVUV,
+ .bpp = { 8, 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV61,
+ .fmt_val = YUV_OUTPUT_422 | UV_STORAGE_ORDER_VUVU,
+ .cplanes = 2,
+ .mplanes = 1,
+ .bpp = { 8, 16 },
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_UVUV,
+ .cplanes = 2,
+ .mplanes = 1,
+ .bpp = { 8, 16 },
+ .mbus = MEDIA_BUS_FMT_UYVY8_2X8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21,
+ .fmt_val = YUV_OUTPUT_420 | UV_STORAGE_ORDER_VUVU,
+ .cplanes = 2,
+ .mplanes = 1,
+ .bpp = { 8, 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 24 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR666,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 18 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 8 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 8 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 8 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 8 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB10,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG10,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR10,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB12,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG12,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG12,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR12,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR16,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y16,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 16 },
+ }
+};
+
+static const struct cif_input_fmt in_fmts[] = {
+ {
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_YUYV,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_YUYV,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_INTERLACED,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_YVYU,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_YVYU,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_INTERLACED,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_UYVY,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_UYVY,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_INTERLACED,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_VYUY,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .dvp_fmt_val = YUV_INPUT_422 | YUV_INPUT_ORDER_VYUY,
+ .csi_fmt_val = CSI_WRDDR_TYPE_YUV422,
+ .fmt_type = CIF_FMT_TYPE_YUV,
+ .field = V4L2_FIELD_INTERLACED,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_8,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW8,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_8,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW8,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_8,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW8,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_8,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW8,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_10,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW10,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_10,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW10,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_10,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW10,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_10,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW10,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_12,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW12,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_12,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW12,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_12,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW12,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_12,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW12,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RGB888,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_8,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW8,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_10,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW10,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_Y12_1X12,
+ .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_12,
+ .csi_fmt_val = CSI_WRDDR_TYPE_RAW12,
+ .fmt_type = CIF_FMT_TYPE_RAW,
+ .field = V4L2_FIELD_NONE,
+ }
+};
+
+static const struct
+cif_input_fmt *get_input_fmt(struct v4l2_subdev *sd)
+{
+ struct v4l2_subdev_format fmt;
+ int ret;
+ u32 i;
+
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ fmt.pad = 0;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
+ if (ret < 0) {
+ v4l2_warn(sd->v4l2_dev,
+ "sensor fmt invalid, set to default size\n");
+ goto set_default;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(in_fmts); i++)
+ if (fmt.format.code == in_fmts[i].mbus_code &&
+ fmt.format.field == in_fmts[i].field)
+ return &in_fmts[i];
+
+ v4l2_err(sd->v4l2_dev, "remote sensor mbus code not supported\n");
+
+set_default:
+ return NULL;
+}
+
+ static const struct
+cif_output_fmt *find_output_fmt(struct rkcif_stream *stream, u32 pixelfmt)
+{
+ const struct cif_output_fmt *fmt;
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(out_fmts); i++) {
+ fmt = &out_fmts[i];
+ if (fmt->fourcc == pixelfmt)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+/***************************** stream operations ******************************/
+static void rkcif_assign_new_buffer_oneframe(struct rkcif_stream *stream)
+{
+ struct rkcif_dummy_buffer *dummy_buf = &stream->dummy_buf;
+ struct rkcif_device *dev = stream->cifdev;
+ void __iomem *base = dev->base_addr;
+
+ /* Set up an empty buffer for the next frame */
+ spin_lock(&stream->vbq_lock);
+ if (!list_empty(&stream->buf_head)) {
+ stream->curr_buf = list_first_entry(&stream->buf_head,
+ struct rkcif_buffer, queue);
+ list_del(&stream->curr_buf->queue);
+ } else {
+ stream->curr_buf = NULL;
+ }
+ spin_unlock(&stream->vbq_lock);
+
+ if (stream->curr_buf) {
+ write_cif_reg(base, CIF_FRM0_ADDR_Y,
+ stream->curr_buf->buff_addr[RKCIF_PLANE_Y]);
+ write_cif_reg(base, CIF_FRM0_ADDR_UV,
+ stream->curr_buf->buff_addr[RKCIF_PLANE_CBCR]);
+ write_cif_reg(base, CIF_FRM1_ADDR_Y,
+ stream->curr_buf->buff_addr[RKCIF_PLANE_Y]);
+ write_cif_reg(base, CIF_FRM1_ADDR_UV,
+ stream->curr_buf->buff_addr[RKCIF_PLANE_CBCR]);
+ } else {
+ write_cif_reg(base, CIF_FRM0_ADDR_Y, dummy_buf->dma_addr);
+ write_cif_reg(base, CIF_FRM0_ADDR_UV, dummy_buf->dma_addr);
+ write_cif_reg(base, CIF_FRM1_ADDR_Y, dummy_buf->dma_addr);
+ write_cif_reg(base, CIF_FRM1_ADDR_UV, dummy_buf->dma_addr);
+ }
+}
+
+static void rkcif_stream_stop(struct rkcif_stream *stream)
+{
+ struct rkcif_device *cif_dev = stream->cifdev;
+ void __iomem *base = cif_dev->base_addr;
+ u32 val;
+
+ val = read_cif_reg(base, CIF_CTRL);
+ write_cif_reg(base, CIF_CTRL, val & (~ENABLE_CAPTURE));
+ write_cif_reg(base, CIF_INTEN, 0x0);
+ write_cif_reg(base, CIF_INTSTAT, 0x3ff);
+ write_cif_reg(base, CIF_FRAME_STATUS, 0x0);
+
+ stream->state = RKCIF_STATE_READY;
+}
+
+static int rkcif_queue_setup(struct vb2_queue *queue,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct rkcif_stream *stream = queue->drv_priv;
+ const struct v4l2_pix_format_mplane *pixm;
+ const struct cif_output_fmt *cif_fmt;
+ u32 i;
+
+ pixm = &stream->pixm;
+ cif_fmt = stream->cif_fmt_out;
+
+ if (*num_planes) {
+ if (*num_planes != cif_fmt->mplanes)
+ return -EINVAL;
+
+ for (i = 0; i < cif_fmt->mplanes; i++)
+ if (sizes[i] < pixm->plane_fmt[i].sizeimage)
+ return -EINVAL;
+ return 0;
+ }
+
+ *num_planes = cif_fmt->mplanes;
+
+ for (i = 0; i < cif_fmt->mplanes; i++) {
+ const struct v4l2_plane_pix_format *plane_fmt;
+
+ plane_fmt = &pixm->plane_fmt[i];
+ sizes[i] = plane_fmt->sizeimage;
+ }
+
+ return 0;
+}
+
+/*
+ * The vb2_buffer are stored in rkcif_buffer, in order to unify
+ * mplane buffer and none-mplane buffer.
+ */
+static void rkcif_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct rkcif_buffer *cifbuf = to_rkcif_buffer(vbuf);
+ struct vb2_queue *queue = vb->vb2_queue;
+ struct rkcif_stream *stream = queue->drv_priv;
+ struct v4l2_pix_format_mplane *pixm = &stream->pixm;
+ const struct cif_output_fmt *fmt = stream->cif_fmt_out;
+ unsigned long lock_flags = 0;
+ int i;
+
+ memset(cifbuf->buff_addr, 0, sizeof(cifbuf->buff_addr));
+ /* If mplanes > 1, every c-plane has its own m-plane,
+ * otherwise, multiple c-planes are in the same m-plane
+ */
+ for (i = 0; i < fmt->mplanes; i++)
+ cifbuf->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
+
+ if (fmt->mplanes == 1) {
+ for (i = 0; i < fmt->cplanes - 1; i++)
+ cifbuf->buff_addr[i + 1] = cifbuf->buff_addr[i] +
+ pixm->plane_fmt[i].bytesperline * pixm->height;
+ }
+
+ spin_lock_irqsave(&stream->vbq_lock, lock_flags);
+ list_add_tail(&cifbuf->queue, &stream->buf_head);
+ spin_unlock_irqrestore(&stream->vbq_lock, lock_flags);
+}
+
+static int rkcif_create_dummy_buf(struct rkcif_stream *stream)
+{
+ struct rkcif_dummy_buffer *dummy_buf = &stream->dummy_buf;
+ struct rkcif_device *dev = stream->cifdev;
+
+ /* get a maximum plane size */
+ dummy_buf->size = max3(stream->pixm.plane_fmt[0].bytesperline *
+ stream->pixm.height,
+ stream->pixm.plane_fmt[1].sizeimage,
+ stream->pixm.plane_fmt[2].sizeimage);
+
+ dummy_buf->vaddr = dma_alloc_coherent(dev->dev, dummy_buf->size,
+ &dummy_buf->dma_addr,
+ GFP_KERNEL);
+ if (!dummy_buf->vaddr) {
+ v4l2_err(&dev->v4l2_dev,
+ "Failed to allocate the memory for dummy buffer\n");
+ return -ENOMEM;
+ }
+
+ v4l2_info(&dev->v4l2_dev, "Allocate dummy buffer, size: 0x%08x\n",
+ dummy_buf->size);
+
+ return 0;
+}
+
+static void rkcif_destroy_dummy_buf(struct rkcif_stream *stream)
+{
+ struct rkcif_dummy_buffer *dummy_buf = &stream->dummy_buf;
+ struct rkcif_device *dev = stream->cifdev;
+
+ dma_free_coherent(dev->dev, dummy_buf->size,
+ dummy_buf->vaddr, dummy_buf->dma_addr);
+}
+
+static void rkcif_stop_streaming(struct vb2_queue *queue)
+{
+ struct rkcif_stream *stream = queue->drv_priv;
+ struct rkcif_device *dev = stream->cifdev;
+ struct rkcif_buffer *buf;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ stream->stopping = true;
+ ret = wait_event_timeout(stream->wq_stopped,
+ stream->state != RKCIF_STATE_STREAMING,
+ msecs_to_jiffies(1000));
+ if (!ret) {
+ rkcif_stream_stop(stream);
+ stream->stopping = false;
+ }
+ pm_runtime_put(dev->dev);
+
+ /* stop the sub device*/
+ sd = dev->sensor.sd;
+ v4l2_subdev_call(sd, video, s_stream, 0);
+ v4l2_subdev_call(sd, core, s_power, 0);
+
+ /* release buffers */
+ if (stream->curr_buf) {
+ list_add_tail(&stream->curr_buf->queue, &stream->buf_head);
+ stream->curr_buf = NULL;
+ }
+ if (stream->next_buf) {
+ list_add_tail(&stream->next_buf->queue, &stream->buf_head);
+ stream->next_buf = NULL;
+ }
+
+ while (!list_empty(&stream->buf_head)) {
+ buf = list_first_entry(&stream->buf_head,
+ struct rkcif_buffer, queue);
+ list_del(&buf->queue);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ rkcif_destroy_dummy_buf(stream);
+}
+
+static u32 rkcif_determine_input_mode(struct rkcif_device *dev)
+{
+ struct rkcif_sensor_info *sensor_info = &dev->sensor;
+ struct rkcif_stream *stream = &dev->stream;
+ v4l2_std_id std;
+ u32 mode = INPUT_MODE_YUV;
+ int ret;
+
+ ret = v4l2_subdev_call(sensor_info->sd, video, querystd, &std);
+ if (ret == 0) {
+ /* retrieve std from sensor if exist */
+ switch (std) {
+ case V4L2_STD_NTSC:
+ mode = INPUT_MODE_NTSC;
+ break;
+ case V4L2_STD_PAL:
+ mode = INPUT_MODE_PAL;
+ break;
+ case V4L2_STD_ATSC:
+ mode = INPUT_MODE_BT1120;
+ break;
+ default:
+ v4l2_err(&dev->v4l2_dev,
+ "std: %lld is not supported", std);
+ }
+ } else {
+ /* determine input mode by mbus_code (fmt_type) */
+ switch (stream->cif_fmt_in->fmt_type) {
+ case CIF_FMT_TYPE_YUV:
+ mode = INPUT_MODE_YUV;
+ break;
+ case CIF_FMT_TYPE_RAW:
+ mode = INPUT_MODE_RAW;
+ break;
+ }
+ }
+
+ return mode;
+}
+
+static inline u32 rkcif_scl_ctl(struct rkcif_stream *stream)
+{
+ u32 fmt_type = stream->cif_fmt_in->fmt_type;
+
+ return (fmt_type == CIF_FMT_TYPE_YUV) ?
+ ENABLE_YUV_16BIT_BYPASS : ENABLE_RAW_16BIT_BYPASS;
+}
+
+static int rkcif_stream_start(struct rkcif_stream *stream)
+{
+ u32 val, mbus_flags, href_pol, vsync_pol,
+ xfer_mode = 0, yc_swap = 0, skip_top = 0;
+ struct rkcif_device *dev = stream->cifdev;
+ struct rkcif_sensor_info *sensor_info;
+ void __iomem *base = dev->base_addr;
+
+ sensor_info = &dev->sensor;
+ stream->frame_idx = 0;
+
+ mbus_flags = sensor_info->mbus.flags;
+ href_pol = (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) ?
+ HSY_HIGH_ACTIVE : HSY_LOW_ACTIVE;
+ vsync_pol = (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) ?
+ VSY_HIGH_ACTIVE : VSY_LOW_ACTIVE;
+
+ if (rkcif_determine_input_mode(dev) == INPUT_MODE_BT1120) {
+ if (stream->cif_fmt_in->field == V4L2_FIELD_NONE)
+ xfer_mode = BT1120_TRANSMIT_PROGRESS;
+ else
+ xfer_mode = BT1120_TRANSMIT_INTERFACE;
+ if (!CIF_FETCH_IS_Y_FIRST(stream->cif_fmt_in->dvp_fmt_val))
+ yc_swap = BT1120_YC_SWAP;
+ }
+
+ val = vsync_pol | href_pol | rkcif_determine_input_mode(dev) |
+ stream->cif_fmt_out->fmt_val | stream->cif_fmt_in->dvp_fmt_val |
+ xfer_mode | yc_swap;
+ write_cif_reg(base, CIF_FOR, val);
+ val = stream->pixm.width;
+ if (stream->cif_fmt_in->fmt_type == CIF_FMT_TYPE_RAW)
+ val = stream->pixm.width * 2;
+ write_cif_reg(base, CIF_VIR_LINE_WIDTH, val);
+ write_cif_reg(base, CIF_SET_SIZE,
+ stream->pixm.width | (stream->pixm.height << 16));
+
+ v4l2_subdev_call(sensor_info->sd, sensor, g_skip_top_lines, &skip_top);
+
+ write_cif_reg(base, CIF_CROP, skip_top << CIF_CROP_Y_SHIFT);
+ write_cif_reg(base, CIF_FRAME_STATUS, FRAME_STAT_CLS);
+ write_cif_reg(base, CIF_INTSTAT, INTSTAT_CLS);
+ write_cif_reg(base, CIF_SCL_CTRL, rkcif_scl_ctl(stream));
+
+ rkcif_assign_new_buffer_oneframe(stream);
+
+ write_cif_reg(base, CIF_INTEN, FRAME_END_EN | LINE_ERR_EN |
+ PST_INF_FRAME_END);
+
+ if (dev->data->chip_id == CHIP_RK1808_CIF &&
+ rkcif_determine_input_mode(dev) == INPUT_MODE_BT1120)
+ write_cif_reg(base, CIF_CTRL,
+ AXI_BURST_16 | MODE_PINGPONG | ENABLE_CAPTURE);
+ else
+ write_cif_reg(base, CIF_CTRL,
+ AXI_BURST_16 | MODE_ONEFRAME | ENABLE_CAPTURE);
+
+ stream->state = RKCIF_STATE_STREAMING;
+
+ return 0;
+}
+
+static int rkcif_start_streaming(struct vb2_queue *queue, unsigned int count)
+{
+ struct rkcif_stream *stream = queue->drv_priv;
+ struct rkcif_device *dev = stream->cifdev;
+ struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ if (WARN_ON(stream->state != RKCIF_STATE_READY)) {
+ ret = -EBUSY;
+ v4l2_err(v4l2_dev, "stream in busy state\n");
+ goto destroy_buf;
+ }
+
+ stream->cif_fmt_in = get_input_fmt(dev->sensor.sd);
+
+ ret = rkcif_create_dummy_buf(stream);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "Failed to create dummy_buf, %d\n", ret);
+ goto destroy_buf;
+ }
+
+ ret = pm_runtime_get_sync(dev->dev);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "Failed to get runtime pm, %d\n", ret);
+ goto destroy_dummy_buf;
+ }
+
+ /* start sub-devices */
+ sd = dev->sensor.sd;
+ ret = v4l2_subdev_call(sd, core, s_power, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ goto runtime_put;
+ ret = v4l2_subdev_call(sd, video, s_stream, 1);
+ if (ret < 0)
+ goto subdev_poweroff;
+
+ ret = rkcif_stream_start(stream);
+ if (ret < 0)
+ goto stop_stream;
+
+ return 0;
+
+stop_stream:
+ rkcif_stream_stop(stream);
+subdev_poweroff:
+ v4l2_subdev_call(sd, core, s_power, 0);
+runtime_put:
+ pm_runtime_put(dev->dev);
+destroy_dummy_buf:
+ rkcif_destroy_dummy_buf(stream);
+destroy_buf:
+ while (!list_empty(&stream->buf_head)) {
+ struct rkcif_buffer *buf;
+
+ buf = list_first_entry(&stream->buf_head,
+ struct rkcif_buffer, queue);
+ list_del(&buf->queue);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ }
+
+ return ret;
+}
+
+static struct vb2_ops rkcif_vb2_ops = {
+ .queue_setup = rkcif_queue_setup,
+ .buf_queue = rkcif_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .stop_streaming = rkcif_stop_streaming,
+ .start_streaming = rkcif_start_streaming,
+};
+
+static int rkcif_init_vb2_queue(struct vb2_queue *q,
+ struct rkcif_stream *stream,
+ enum v4l2_buf_type buf_type)
+{
+ q->type = buf_type;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = stream;
+ q->ops = &rkcif_vb2_ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct rkcif_buffer);
+ q->min_buffers_needed = CIF_REQ_BUFS_MIN;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &stream->vlock;
+ q->dev = stream->cifdev->dev;
+
+ return vb2_queue_init(q);
+}
+
+static void rkcif_set_fmt(struct rkcif_stream *stream,
+ struct v4l2_pix_format_mplane *pixm,
+ bool try)
+{
+ const struct cif_output_fmt *fmt;
+ struct v4l2_rect input_rect;
+ unsigned int imagesize = 0, planes;
+ u32 xsubs = 1, ysubs = 1, i;
+
+ fmt = find_output_fmt(stream, pixm->pixelformat);
+ if (!fmt)
+ fmt = &out_fmts[0];
+
+ input_rect.width = CIF_MAX_WIDTH;
+ input_rect.height = CIF_MAX_HEIGHT;
+
+ pixm->width = clamp_t(u32, pixm->width,
+ CIF_MIN_WIDTH, input_rect.width);
+ pixm->height = clamp_t(u32, pixm->height,
+ CIF_MIN_HEIGHT, input_rect.height);
+
+ pixm->num_planes = fmt->mplanes;
+ pixm->quantization = V4L2_QUANTIZATION_DEFAULT;
+ pixm->colorspace = V4L2_COLORSPACE_SRGB;
+
+ pixm->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pixm->colorspace);
+ pixm->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pixm->colorspace);
+
+ pixm->pixelformat = fmt->fourcc;
+ pixm->field = V4L2_FIELD_NONE;
+
+ /* calculate plane size and image size */
+ fcc_xysubs(fmt->fourcc, &xsubs, &ysubs);
+
+ planes = fmt->cplanes ? fmt->cplanes : fmt->mplanes;
+
+ for (i = 0; i < planes; i++) {
+ struct v4l2_plane_pix_format *plane_fmt;
+ int width, height, bpl, size;
+
+ if (i == 0) {
+ width = pixm->width;
+ height = pixm->height;
+ } else {
+ width = pixm->width / xsubs;
+ height = pixm->height / ysubs;
+ }
+
+ bpl = width * fmt->bpp[i] / 8;
+ size = bpl * height;
+ imagesize += size;
+
+ if (fmt->mplanes > i) {
+ /* Set bpl and size for each mplane */
+ plane_fmt = pixm->plane_fmt + i;
+ plane_fmt->bytesperline = bpl;
+ plane_fmt->sizeimage = size;
+ }
+ }
+
+ /* convert to non-MPLANE format.
+ * It's important since we want to unify non-MPLANE
+ * and MPLANE.
+ */
+ if (fmt->mplanes == 1)
+ pixm->plane_fmt[0].sizeimage = imagesize;
+
+ if (!try) {
+ stream->cif_fmt_out = fmt;
+ stream->pixm = *pixm;
+ }
+}
+
+void rkcif_stream_init(struct rkcif_device *dev)
+{
+ struct rkcif_stream *stream = &dev->stream;
+ struct v4l2_pix_format_mplane pixm;
+
+ memset(stream, 0, sizeof(*stream));
+ memset(&pixm, 0, sizeof(pixm));
+ stream->cifdev = dev;
+
+ INIT_LIST_HEAD(&stream->buf_head);
+ spin_lock_init(&stream->vbq_lock);
+ stream->state = RKCIF_STATE_READY;
+ init_waitqueue_head(&stream->wq_stopped);
+
+ /* Set default format */
+ pixm.pixelformat = V4L2_PIX_FMT_NV12;
+ pixm.width = RKCIF_DEFAULT_WIDTH;
+ pixm.height = RKCIF_DEFAULT_HEIGHT;
+ rkcif_set_fmt(stream, &pixm, false);
+
+ stream->crop.left = 0;
+ stream->crop.top = 0;
+ stream->crop.width = 10;
+ stream->crop.height = 10;
+}
+
+static int rkcif_fh_open(struct file *filp)
+{
+ struct video_device *vdev = video_devdata(filp);
+ struct rkcif_stream *stream = to_rkcif_stream(vdev);
+ struct rkcif_device *cifdev = stream->cifdev;
+
+ rkcif_soft_reset(cifdev);
+
+ return v4l2_fh_open(filp);
+}
+
+static const struct v4l2_file_operations rkcif_fops = {
+ .open = rkcif_fh_open,
+ .release = vb2_fop_release,
+ .unlocked_ioctl = video_ioctl2,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+};
+
+static int rkcif_enum_input(struct file *file, void *priv,
+ struct v4l2_input *input)
+{
+ if (input->index > 0)
+ return -EINVAL;
+
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ strlcpy(input->name, "Camera", sizeof(input->name));
+
+ return 0;
+}
+
+static int rkcif_try_fmt_vid_cap_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct rkcif_stream *stream = video_drvdata(file);
+
+ rkcif_set_fmt(stream, &f->fmt.pix_mp, true);
+
+ return 0;
+}
+
+static int rkcif_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ const struct cif_output_fmt *fmt = NULL;
+
+ if (f->index >= ARRAY_SIZE(out_fmts))
+ return -EINVAL;
+
+ fmt = &out_fmts[f->index];
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int rkcif_s_fmt_vid_cap_mplane(struct file *file,
+ void *priv, struct v4l2_format *f)
+{
+ struct rkcif_stream *stream = video_drvdata(file);
+
+ rkcif_set_fmt(stream, &f->fmt.pix_mp, false);
+
+ return 0;
+}
+
+static int rkcif_g_fmt_vid_cap_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct rkcif_stream *stream = video_drvdata(file);
+
+ f->fmt.pix_mp = stream->pixm;
+
+ return 0;
+}
+
+static int rkcif_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct rkcif_stream *stream = video_drvdata(file);
+ struct device *dev = stream->cifdev->dev;
+
+ strlcpy(cap->driver, dev->driver->name, sizeof(cap->driver));
+ strlcpy(cap->card, dev->driver->name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "platform:%s", dev_name(dev));
+
+ return 0;
+}
+
+static int rkcif_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct rkcif_stream *stream = video_drvdata(file);
+ struct rkcif_device *dev = stream->cifdev;
+ struct v4l2_subdev_frame_size_enum fse = {
+ .index = fsize->index,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ const struct cif_output_fmt *fmt;
+ int ret;
+
+ if (!dev->sensor.sd)
+ return -EINVAL;
+
+ fmt = find_output_fmt(stream, fsize->pixel_format);
+ if (!fmt)
+ return -EINVAL;
+
+ fse.code = fmt->mbus;
+
+ ret = v4l2_subdev_call(dev->sensor.sd, pad, enum_frame_size,
+ NULL, &fse);
+ if (ret)
+ return ret;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = fse.max_width;
+ fsize->discrete.height = fse.max_height;
+
+ return 0;
+}
+
+static int rkcif_g_input(struct file *file, void *fh, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int rkcif_s_input(struct file *file, void *fh, unsigned int i)
+{
+ if (i)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops rkcif_v4l2_ioctl_ops = {
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_enum_fmt_vid_cap = rkcif_enum_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap_mplane = rkcif_try_fmt_vid_cap_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = rkcif_s_fmt_vid_cap_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = rkcif_g_fmt_vid_cap_mplane,
+ .vidioc_querycap = rkcif_querycap,
+ .vidioc_enum_framesizes = rkcif_enum_framesizes,
+
+ .vidioc_enum_input = rkcif_enum_input,
+ .vidioc_g_input = rkcif_g_input,
+ .vidioc_s_input = rkcif_s_input,
+};
+
+void rkcif_unregister_stream_vdev(struct rkcif_device *dev)
+{
+ struct rkcif_stream *stream = &dev->stream;
+
+ media_entity_cleanup(&stream->vdev.entity);
+ video_unregister_device(&stream->vdev);
+}
+
+int rkcif_register_stream_vdev(struct rkcif_device *dev)
+{
+ struct rkcif_stream *stream = &dev->stream;
+ struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
+ struct video_device *vdev = &stream->vdev;
+ int ret;
+
+ strlcpy(vdev->name, CIF_VIDEODEVICE_NAME, sizeof(vdev->name));
+ mutex_init(&stream->vlock);
+
+ vdev->ioctl_ops = &rkcif_v4l2_ioctl_ops;
+ vdev->release = video_device_release_empty;
+ vdev->fops = &rkcif_fops;
+ vdev->minor = -1;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->lock = &stream->vlock;
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE |
+ V4L2_CAP_STREAMING;
+ video_set_drvdata(vdev, stream);
+ vdev->vfl_dir = VFL_DIR_RX;
+ stream->pad.flags = MEDIA_PAD_FL_SINK;
+
+ rkcif_init_vb2_queue(&stream->buf_queue, stream,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE |
+ V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ vdev->queue = &stream->buf_queue;
+ strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev,
+ "video_register_device failed with error %d\n", ret);
+ return ret;
+ }
+
+ ret = media_entity_pads_init(&vdev->entity, 1, &stream->pad);
+ if (ret < 0)
+ goto unreg;
+
+ return 0;
+unreg:
+ video_unregister_device(vdev);
+ return ret;
+}
+
+static void rkcif_vb_done_oneframe(struct rkcif_stream *stream,
+ struct vb2_v4l2_buffer *vb_done)
+{
+ const struct cif_output_fmt *fmt = stream->cif_fmt_out;
+ u32 i;
+
+ /* Dequeue a filled buffer */
+ for (i = 0; i < fmt->mplanes; i++) {
+ vb2_set_plane_payload(&vb_done->vb2_buf, i,
+ stream->pixm.plane_fmt[i].sizeimage);
+ }
+ vb_done->vb2_buf.timestamp = ktime_get_ns();
+ vb2_buffer_done(&vb_done->vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+void rkcif_irq_oneframe(struct rkcif_device *cif_dev)
+{
+ struct rkcif_stream *stream = &cif_dev->stream;
+ u32 lastline, lastpix, ctl, cif_frmst, intstat;
+ void __iomem *base = cif_dev->base_addr;
+
+ intstat = read_cif_reg(base, CIF_INTSTAT);
+ cif_frmst = read_cif_reg(base, CIF_FRAME_STATUS);
+ lastline = CIF_FETCH_Y_LAST_LINE(read_cif_reg(base, CIF_LAST_LINE));
+ lastpix = read_cif_reg(base, CIF_LAST_PIX);
+ ctl = read_cif_reg(base, CIF_CTRL);
+
+ /* There are two irqs enabled:
+ * - PST_INF_FRAME_END: cif FIFO is ready, this is prior to FRAME_END
+ * - FRAME_END: cif has saved frame to memory, a frame ready
+ */
+
+ if ((intstat & PST_INF_FRAME_END)) {
+ write_cif_reg(base, CIF_INTSTAT, PST_INF_FRAME_END_CLR);
+
+ if (stream->stopping)
+ /* To stop CIF ASAP, before FRAME_END irq */
+ write_cif_reg(base, CIF_CTRL, ctl & (~ENABLE_CAPTURE));
+ }
+
+ if ((intstat & LINE_ERR)) {
+ write_cif_reg(base, CIF_INTSTAT, LINE_ERR_CLR);
+
+ if (stream->stopping) {
+ rkcif_stream_stop(stream);
+ stream->stopping = false;
+ wake_up(&stream->wq_stopped);
+ return;
+ }
+
+ v4l2_err(&cif_dev->v4l2_dev,
+ "Bad frame, irq:0x%x frmst:0x%x size:%dx%d\n",
+ intstat, cif_frmst, lastline, lastpix);
+ /* Clear status to receive into the same buffer */
+ write_cif_reg(base, CIF_FRAME_STATUS, FRM0_STAT_CLS);
+ return;
+ }
+
+
+ if ((intstat & FRAME_END)) {
+ struct vb2_v4l2_buffer *vb_done = NULL;
+
+ write_cif_reg(base, CIF_INTSTAT, FRAME_END_CLR);
+
+ if (stream->stopping) {
+ rkcif_stream_stop(stream);
+ stream->stopping = false;
+ wake_up(&stream->wq_stopped);
+ return;
+ }
+
+ if (lastline != stream->pixm.height ||
+ !(cif_frmst & CIF_F0_READY)) {
+ v4l2_err(&cif_dev->v4l2_dev,
+ "Bad frame, irq:0x%x frmst:0x%x size:%dx%d\n",
+ intstat, cif_frmst, lastline, lastpix);
+ /* Clear status to receive into the same buffer */
+ write_cif_reg(base, CIF_FRAME_STATUS, FRM0_STAT_CLS);
+ return;
+ }
+
+ if (stream->curr_buf)
+ vb_done = &stream->curr_buf->vb;
+ rkcif_assign_new_buffer_oneframe(stream);
+
+ /* In one-frame mode, must clear status manually to enable
+ * the next frame end irq
+ */
+ write_cif_reg(base, CIF_FRAME_STATUS, FRM0_STAT_CLS);
+
+ if (vb_done)
+ rkcif_vb_done_oneframe(stream, vb_done);
+
+ stream->frame_idx++;
+ }
+}
diff --git a/drivers/media/platform/rockchip/cif/dev.c b/drivers/media/platform/rockchip/cif/dev.c
new file mode 100644
index 000000000000..dbd5fdbd1cef
--- /dev/null
+++ b/drivers/media/platform/rockchip/cif/dev.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip CIF Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/reset.h>
+#include <linux/pm_runtime.h>
+#include <linux/pinctrl/consumer.h>
+#include <media/v4l2-fwnode.h>
+
+#include "dev.h"
+#include "regs.h"
+
+#define RKCIF_VERNO_LEN 10
+
+static int rkcif_create_links(struct rkcif_device *dev)
+{
+ struct v4l2_subdev *sd = dev->sensor.sd;
+ int ret;
+
+ ret = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (ret)
+ return ret;
+
+ ret = media_create_pad_link(&sd->entity, 0,
+ &dev->stream.vdev.entity, 0,
+ MEDIA_LNK_FL_ENABLED);
+ if (ret) {
+ dev_err(dev->dev, "failed to create link");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rkcif_device *dev;
+ int ret;
+
+ dev = container_of(notifier, struct rkcif_device, notifier);
+
+ mutex_lock(&dev->media_dev.graph_mutex);
+
+ ret = rkcif_create_links(dev);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev);
+ if (ret < 0)
+ goto unlock;
+
+unlock:
+ mutex_unlock(&dev->media_dev.graph_mutex);
+ return ret;
+}
+
+static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rkcif_device *cif_dev = container_of(notifier,
+ struct rkcif_device, notifier);
+
+ int pad;
+
+ cif_dev->sensor.sd = subdev;
+ pad = media_entity_get_fwnode_pad(&subdev->entity, subdev->fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (pad < 0)
+ return pad;
+
+ cif_dev->sensor.pad = pad;
+
+ return 0;
+}
+
+static const struct v4l2_async_notifier_operations subdev_notifier_ops = {
+ .bound = subdev_notifier_bound,
+ .complete = subdev_notifier_complete,
+};
+
+static int cif_subdev_notifier(struct rkcif_device *cif_dev)
+{
+ struct v4l2_async_notifier *ntf = &cif_dev->notifier;
+ struct device *dev = cif_dev->dev;
+ struct v4l2_fwnode_endpoint vep = {
+ .bus_type = V4L2_MBUS_PARALLEL,
+ };
+ struct fwnode_handle *ep;
+ int ret;
+
+ v4l2_async_notifier_init(ntf);
+
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+ if (!ep)
+ return -EINVAL;
+
+ ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+ if (ret)
+ return ret;
+
+ ret = v4l2_async_notifier_add_fwnode_remote_subdev(ntf, ep,
+ &cif_dev->asd);
+ if (ret)
+ return ret;
+
+ ntf->ops = &subdev_notifier_ops;
+
+ fwnode_handle_put(ep);
+
+ ret = v4l2_async_notifier_register(&cif_dev->v4l2_dev, ntf);
+ return ret;
+}
+
+static int rkcif_register_platform_subdevs(struct rkcif_device *cif_dev)
+{
+ int ret;
+
+ ret = rkcif_register_stream_vdev(cif_dev);
+ if (ret < 0)
+ return ret;
+
+ ret = cif_subdev_notifier(cif_dev);
+ if (ret < 0) {
+ v4l2_err(&cif_dev->v4l2_dev,
+ "Failed to register subdev notifier(%d)\n", ret);
+ rkcif_unregister_stream_vdev(cif_dev);
+ }
+
+ return 0;
+}
+
+static struct clk_bulk_data px30_cif_clks[] = {
+ { .id = "aclk" },
+ { .id = "hclk" },
+ { .id = "pclkin" },
+};
+
+static const struct cif_match_data px30_cif_match_data = {
+ .chip_id = CHIP_PX30_CIF,
+ .clks = px30_cif_clks,
+ .clks_num = ARRAY_SIZE(px30_cif_clks),
+};
+
+static const struct of_device_id rkcif_plat_of_match[] = {
+ {
+ .compatible = "rockchip,px30-cif",
+ .data = &px30_cif_match_data,
+ },
+ {},
+};
+
+static irqreturn_t rkcif_irq_handler(int irq, void *ctx)
+{
+ struct device *dev = ctx;
+ struct rkcif_device *cif_dev = dev_get_drvdata(dev);
+
+ rkcif_irq_oneframe(cif_dev);
+
+ return IRQ_HANDLED;
+}
+
+static void rkcif_disable_sys_clk(struct rkcif_device *cif_dev)
+{
+ int i;
+
+ for (i = cif_dev->data->clks_num - 1; i >= 0; i--)
+ clk_disable_unprepare(cif_dev->clks[i]);
+}
+
+static int rkcif_enable_sys_clk(struct rkcif_device *cif_dev)
+{
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < cif_dev->data->clks_num; i++) {
+ ret = clk_prepare_enable(cif_dev->clks[i]);
+
+ if (ret < 0)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ for (--i; i >= 0; --i)
+ clk_disable_unprepare(cif_dev->clks[i]);
+
+ return ret;
+}
+
+void rkcif_soft_reset(struct rkcif_device *cif_dev)
+{
+ reset_control_assert(cif_dev->cif_rst);
+
+ udelay(5);
+
+ reset_control_deassert(cif_dev->cif_rst);
+}
+
+static int rkcif_plat_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct v4l2_device *v4l2_dev;
+ struct rkcif_device *cif_dev;
+ const struct cif_match_data *data;
+ int ret, irq;
+
+ cif_dev = devm_kzalloc(dev, sizeof(*cif_dev), GFP_KERNEL);
+ if (!cif_dev)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, cif_dev);
+ cif_dev->dev = dev;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data)
+ return -EINVAL;
+
+ cif_dev->data = data;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(dev, irq, rkcif_irq_handler, IRQF_SHARED,
+ dev_driver_string(dev), dev);
+ if (ret < 0) {
+ dev_err(dev, "request irq failed: %d\n", ret);
+ return ret;
+ }
+
+ cif_dev->irq = irq;
+
+ cif_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(cif_dev->base_addr))
+ return PTR_ERR(cif_dev->base_addr);
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret)
+ v4l2_info(v4l2_dev, "No reserved memory region assign to CIF\n");
+
+ ret = devm_clk_bulk_get(dev, data->clks_num, data->clks);
+ if (ret)
+ return ret;
+
+ cif_dev->cif_rst = devm_reset_control_array_get(dev, false, false);
+ if (IS_ERR(cif_dev->cif_rst))
+ return PTR_ERR(cif_dev->cif_rst);
+
+ /* Initialize the stream */
+ rkcif_stream_init(cif_dev);
+
+ strlcpy(cif_dev->media_dev.model, "rkcif",
+ sizeof(cif_dev->media_dev.model));
+ cif_dev->media_dev.dev = &pdev->dev;
+ v4l2_dev = &cif_dev->v4l2_dev;
+ v4l2_dev->mdev = &cif_dev->media_dev;
+ strlcpy(v4l2_dev->name, "rkcif", sizeof(v4l2_dev->name));
+ v4l2_ctrl_handler_init(&cif_dev->ctrl_handler, 8);
+ v4l2_dev->ctrl_handler = &cif_dev->ctrl_handler;
+
+ ret = v4l2_device_register(cif_dev->dev, &cif_dev->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ media_device_init(&cif_dev->media_dev);
+
+ ret = media_device_register(&cif_dev->media_dev);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "Failed to register media device: %d\n",
+ ret);
+ goto err_unreg_v4l2_dev;
+ }
+
+ /* create & register platefom subdev (from of_node) */
+ ret = rkcif_register_platform_subdevs(cif_dev);
+ if (ret < 0)
+ goto err_unreg_media_dev;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+
+err_unreg_media_dev:
+ media_device_unregister(&cif_dev->media_dev);
+err_unreg_v4l2_dev:
+ v4l2_device_unregister(&cif_dev->v4l2_dev);
+ return ret;
+}
+
+static int rkcif_plat_remove(struct platform_device *pdev)
+{
+ struct rkcif_device *cif_dev = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ media_device_unregister(&cif_dev->media_dev);
+ v4l2_device_unregister(&cif_dev->v4l2_dev);
+ rkcif_unregister_stream_vdev(cif_dev);
+
+ return 0;
+}
+
+static int __maybe_unused rkcif_runtime_suspend(struct device *dev)
+{
+ struct rkcif_device *cif_dev = dev_get_drvdata(dev);
+
+ rkcif_disable_sys_clk(cif_dev);
+
+ return pinctrl_pm_select_sleep_state(dev);
+}
+
+static int __maybe_unused rkcif_runtime_resume(struct device *dev)
+{
+ struct rkcif_device *cif_dev = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pinctrl_pm_select_default_state(dev);
+ if (ret < 0)
+ return ret;
+ rkcif_enable_sys_clk(cif_dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops rkcif_plat_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(rkcif_runtime_suspend, rkcif_runtime_resume, NULL)
+};
+
+static struct platform_driver rkcif_plat_drv = {
+ .driver = {
+ .name = CIF_DRIVER_NAME,
+ .of_match_table = of_match_ptr(rkcif_plat_of_match),
+ .pm = &rkcif_plat_pm_ops,
+ },
+ .probe = rkcif_plat_probe,
+ .remove = rkcif_plat_remove,
+};
+
+module_platform_driver(rkcif_plat_drv);
+MODULE_AUTHOR("Rockchip Camera/ISP team");
+MODULE_DESCRIPTION("Rockchip CIF platform driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/rockchip/cif/dev.h b/drivers/media/platform/rockchip/cif/dev.h
new file mode 100644
index 000000000000..2126c2220ad2
--- /dev/null
+++ b/drivers/media/platform/rockchip/cif/dev.h
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip CIF Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ */
+
+#ifndef _RKCIF_DEV_H
+#define _RKCIF_DEV_H
+
+#include <linux/mutex.h>
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+
+#define CIF_DRIVER_NAME "rkcif"
+#define CIF_VIDEODEVICE_NAME "stream_cif"
+
+#define RKCIF_MAX_BUS_CLK 8
+#define RKCIF_MAX_SENSOR 2
+#define RKCIF_MAX_RESET 5
+#define RKCIF_MAX_CSI_CHANNEL 4
+
+#define RKCIF_DEFAULT_WIDTH 640
+#define RKCIF_DEFAULT_HEIGHT 480
+
+#define write_cif_reg(base, addr, val) writel(val, (addr) + (base))
+#define read_cif_reg(base, addr) readl((addr) + (base))
+
+#define write_csihost_reg(base, addr, val) writel(val, (addr) + (base))
+#define read_csihost_reg(base, addr) readl((addr) + (base))
+
+enum rkcif_state {
+ RKCIF_STATE_DISABLED,
+ RKCIF_STATE_READY,
+ RKCIF_STATE_STREAMING
+};
+
+enum rkcif_chip_id {
+ CHIP_PX30_CIF,
+ CHIP_RK1808_CIF,
+ CHIP_RK3128_CIF,
+ CHIP_RK3288_CIF
+};
+
+enum host_type_t {
+ RK_CSI_RXHOST,
+ RK_DSI_RXHOST
+};
+
+struct rkcif_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head queue;
+ union {
+ u32 buff_addr[VIDEO_MAX_PLANES];
+ void *vaddr[VIDEO_MAX_PLANES];
+ };
+};
+
+struct rkcif_dummy_buffer {
+ void *vaddr;
+ dma_addr_t dma_addr;
+ u32 size;
+};
+
+extern int rkcif_debug;
+
+static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
+{
+ return container_of(vb, struct rkcif_buffer, vb);
+}
+
+/*
+ * struct rkcif_sensor_info - Sensor infomations
+ * @mbus: media bus configuration
+ */
+struct rkcif_sensor_info {
+ struct v4l2_subdev *sd;
+ int pad;
+ struct v4l2_mbus_config mbus;
+ int lanes;
+};
+
+/*
+ * struct cif_output_fmt - The output format
+ *
+ * @fourcc: pixel format in fourcc
+ * @cplanes: number of colour planes
+ * @fmt_val: the fmt val corresponding to CIF_FOR register
+ * @bpp: bits per pixel for each cplanes
+ */
+struct cif_output_fmt {
+ u32 fourcc;
+ u32 mbus;
+ u8 cplanes;
+ u8 mplanes;
+ u32 fmt_val;
+ u8 bpp[VIDEO_MAX_PLANES];
+};
+
+enum cif_fmt_type {
+ CIF_FMT_TYPE_YUV = 0,
+ CIF_FMT_TYPE_RAW,
+};
+
+/*
+ * struct cif_input_fmt - The input mbus format from sensor
+ *
+ * @mbus_code: mbus format
+ * @dvp_fmt_val: the fmt val corresponding to CIF_FOR register
+ * @csi_fmt_val: the fmt val corresponding to CIF_CSI_ID_CTRL
+ * @field: the field type of the input from sensor
+ */
+struct cif_input_fmt {
+ u32 mbus_code;
+ u32 dvp_fmt_val;
+ u32 csi_fmt_val;
+ enum cif_fmt_type fmt_type;
+ enum v4l2_field field;
+};
+
+/*
+ * struct rkcif_stream - Stream states TODO
+ *
+ * @vbq_lock: lock to protect buf_queue
+ * @buf_queue: queued buffer list
+ * @dummy_buf: dummy space to store dropped data
+ *
+ * rkcif use shadowsock registers, so it need two buffer at a time
+ * @curr_buf: the buffer used for current frame
+ * @next_buf: the buffer used for next frame
+ */
+struct rkcif_stream {
+ struct rkcif_device *cifdev;
+ enum rkcif_state state;
+ bool stopping;
+ wait_queue_head_t wq_stopped;
+ int frame_idx;
+ int frame_phase;
+
+ /* lock between irq and buf_queue */
+ spinlock_t vbq_lock;
+ struct vb2_queue buf_queue;
+ struct list_head buf_head;
+ struct rkcif_dummy_buffer dummy_buf;
+ struct rkcif_buffer *curr_buf;
+ struct rkcif_buffer *next_buf;
+
+ /* vfd lock */
+ struct mutex vlock;
+ struct video_device vdev;
+ /* TODO: pad for dvp and mipi separately? */
+ struct media_pad pad;
+
+ const struct cif_output_fmt *cif_fmt_out;
+ const struct cif_input_fmt *cif_fmt_in;
+ struct v4l2_pix_format_mplane pixm;
+ struct v4l2_rect crop;
+ int crop_enable;
+};
+
+static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
+{
+ return container_of(vdev, struct rkcif_stream, vdev);
+}
+
+struct cif_match_data {
+ int chip_id;
+ struct clk_bulk_data *clks;
+ int clks_num;
+};
+
+/*
+ * struct rkcif_device - ISP platform device
+ * @base_addr: base register address
+ * @active_sensor: sensor in-use, set when streaming on
+ * @stream: capture video device
+ */
+struct rkcif_device {
+ struct list_head list;
+ struct device *dev;
+ int irq;
+ void __iomem *base_addr;
+ void __iomem *csi_base;
+ struct clk *clks[RKCIF_MAX_BUS_CLK];
+ int clk_size;
+ struct vb2_alloc_ctx *alloc_ctx;
+ bool iommu_en;
+ struct iommu_domain *domain;
+ struct reset_control *cif_rst;
+
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_async_notifier notifier;
+ struct v4l2_async_subdev asd;
+ struct rkcif_sensor_info sensor;
+
+ struct rkcif_stream stream;
+ const struct cif_match_data *data;
+};
+
+void rkcif_unregister_stream_vdev(struct rkcif_device *dev);
+int rkcif_register_stream_vdev(struct rkcif_device *dev);
+void rkcif_stream_init(struct rkcif_device *dev);
+
+void rkcif_irq_oneframe(struct rkcif_device *cif_dev);
+void rkcif_irq_pingpong(struct rkcif_device *cif_dev);
+void rkcif_soft_reset(struct rkcif_device *cif_dev);
+
+#endif
diff --git a/drivers/media/platform/rockchip/cif/regs.h b/drivers/media/platform/rockchip/cif/regs.h
new file mode 100644
index 000000000000..5e0f926c70d3
--- /dev/null
+++ b/drivers/media/platform/rockchip/cif/regs.h
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip CIF Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ */
+
+#ifndef _RKCIF_REGS_H
+#define _RKCIF_REGS_H
+
+/* CIF Reg Offset */
+#define CIF_CTRL 0x00
+#define CIF_INTEN 0x04
+#define CIF_INTSTAT 0x08
+#define CIF_FOR 0x0c
+#define CIF_LINE_NUM_ADDR 0x10
+#define CIF_FRM0_ADDR_Y 0x14
+#define CIF_FRM0_ADDR_UV 0x18
+#define CIF_FRM1_ADDR_Y 0x1c
+#define CIF_FRM1_ADDR_UV 0x20
+#define CIF_VIR_LINE_WIDTH 0x24
+#define CIF_SET_SIZE 0x28
+#define CIF_SCM_ADDR_Y 0x2c
+#define CIF_SCM_ADDR_U 0x30
+#define CIF_SCM_ADDR_V 0x34
+#define CIF_WB_UP_FILTER 0x38
+#define CIF_WB_LOW_FILTER 0x3c
+#define CIF_WBC_CNT 0x40
+#define CIF_CROP 0x44
+#define CIF_SCL_CTRL 0x48
+#define CIF_SCL_DST 0x4c
+#define CIF_SCL_FCT 0x50
+#define CIF_SCL_VALID_NUM 0x54
+#define CIF_LINE_LOOP_CTR 0x58
+#define CIF_FRAME_STATUS 0x60
+#define CIF_CUR_DST 0x64
+#define CIF_LAST_LINE 0x68
+#define CIF_LAST_PIX 0x6c
+
+/* RK1808 CIF CSI Registers Offset */
+#define CIF_CSI_ID0_CTRL0 0x80
+#define CIF_CSI_ID0_CTRL1 0x84
+#define CIF_CSI_ID1_CTRL0 0x88
+#define CIF_CSI_ID1_CTRL1 0x8c
+#define CIF_CSI_ID2_CTRL0 0x90
+#define CIF_CSI_ID2_CTRL1 0x94
+#define CIF_CSI_ID3_CTRL0 0x98
+#define CIF_CSI_ID3_CTRL1 0x9c
+#define CIF_CSI_WATER_LINE 0xa0
+#define CIF_CSI_FRM0_ADDR_Y_ID0 0xa4
+#define CIF_CSI_FRM1_ADDR_Y_ID0 0xa8
+#define CIF_CSI_FRM0_ADDR_UV_ID0 0xac
+#define CIF_CSI_FRM1_ADDR_UV_ID0 0xb0
+#define CIF_CSI_FRM0_VLW_Y_ID0 0xb4
+#define CIF_CSI_FRM1_VLW_Y_ID0 0xb8
+#define CIF_CSI_FRM0_VLW_UV_ID0 0xbc
+#define CIF_CSI_FRM1_VLW_UV_ID0 0xc0
+#define CIF_CSI_FRM0_ADDR_Y_ID1 0xc4
+#define CIF_CSI_FRM1_ADDR_Y_ID1 0xc8
+#define CIF_CSI_FRM0_ADDR_UV_ID1 0xcc
+#define CIF_CSI_FRM1_ADDR_UV_ID1 0xd0
+#define CIF_CSI_FRM0_VLW_Y_ID1 0xd4
+#define CIF_CSI_FRM1_VLW_Y_ID1 0xd8
+#define CIF_CSI_FRM0_VLW_UV_ID1 0xdc
+#define CIF_CSI_FRM1_VLW_UV_ID1 0xe0
+#define CIF_CSI_FRM0_ADDR_Y_ID2 0xe4
+#define CIF_CSI_FRM1_ADDR_Y_ID2 0xe8
+#define CIF_CSI_FRM0_ADDR_UV_ID2 0xec
+#define CIF_CSI_FRM1_ADDR_UV_ID2 0xf0
+#define CIF_CSI_FRM0_VLW_Y_ID2 0xf4
+#define CIF_CSI_FRM1_VLW_Y_ID2 0xf8
+#define CIF_CSI_FRM0_VLW_UV_ID2 0xfc
+#define CIF_CSI_FRM1_VLW_UV_ID2 0x100
+#define CIF_CSI_FRM0_ADDR_Y_ID3 0x104
+#define CIF_CSI_FRM1_ADDR_Y_ID3 0x108
+#define CIF_CSI_FRM0_ADDR_UV_ID3 0x10c
+#define CIF_CSI_FRM1_ADDR_UV_ID3 0x110
+#define CIF_CSI_FRM0_VLW_Y_ID3 0x114
+#define CIF_CSI_FRM1_VLW_Y_ID3 0x118
+#define CIF_CSI_FRM0_VLW_UV_ID3 0x11c
+#define CIF_CSI_FRM1_VLW_UV_ID3 0x120
+#define CIF_CSI_INTEN 0x124
+#define CIF_CSI_INTSTAT 0x128
+#define CIF_CSI_LINE_INT_NUM_ID0_1 0x12c
+#define CIF_CSI_LINE_INT_NUM_ID2_3 0x130
+#define CIF_CSI_LINE_CNT_ID0_1 0x134
+#define CIF_CSI_LINE_CNT_ID2_3 0x138
+#define CIF_CSI_ID0_CROP_START 0x13c
+#define CIF_CSI_ID1_CROP_START 0x140
+#define CIF_CSI_ID2_CROP_START 0x144
+#define CIF_CSI_ID3_CROP_START 0x148
+
+/* The key register bit description */
+
+/* CIF_CTRL Reg */
+#define DISABLE_CAPTURE (0x0 << 0)
+#define ENABLE_CAPTURE (0x1 << 0)
+#define MODE_ONEFRAME (0x0 << 1)
+#define MODE_PINGPONG (0x1 << 1)
+#define MODE_LINELOOP (0x2 << 1)
+#define AXI_BURST_16 (0xF << 12)
+
+/* CIF_INTEN */
+#define INTEN_DISABLE (0x0 << 0)
+#define FRAME_END_EN (0x1 << 0)
+#define LINE_ERR_EN (0x1 << 2)
+#define BUS_ERR_EN (0x1 << 6)
+#define SCL_ERR_EN (0x1 << 7)
+#define PST_INF_FRAME_END_EN (0x1 << 9)
+
+/* CIF INTSTAT */
+#define INTSTAT_CLS (0x3FF)
+#define FRAME_END (0x01 << 0)
+#define LINE_ERR (0x01 << 2)
+#define PST_INF_FRAME_END (0x01 << 9)
+#define FRAME_END_CLR (0x01 << 0)
+#define LINE_ERR_CLR (0x01 << 2)
+#define PST_INF_FRAME_END_CLR (0x01 << 9)
+#define INTSTAT_ERR (0xFC)
+
+/* FRAME STATUS */
+#define FRAME_STAT_CLS 0x00
+#define FRM0_STAT_CLS 0x20 /* write 0 to clear frame 0 */
+
+/* CIF FORMAT */
+#define VSY_HIGH_ACTIVE (0x01 << 0)
+#define VSY_LOW_ACTIVE (0x00 << 0)
+#define HSY_LOW_ACTIVE (0x01 << 1)
+#define HSY_HIGH_ACTIVE (0x00 << 1)
+#define INPUT_MODE_YUV (0x00 << 2)
+#define INPUT_MODE_PAL (0x02 << 2)
+#define INPUT_MODE_NTSC (0x03 << 2)
+#define INPUT_MODE_BT1120 (0x07 << 2)
+#define INPUT_MODE_RAW (0x04 << 2)
+#define INPUT_MODE_JPEG (0x05 << 2)
+#define INPUT_MODE_MIPI (0x06 << 2)
+#define YUV_INPUT_ORDER_UYVY (0x00 << 5)
+#define YUV_INPUT_ORDER_YVYU (0x01 << 5)
+#define YUV_INPUT_ORDER_VYUY (0x10 << 5)
+#define YUV_INPUT_ORDER_YUYV (0x03 << 5)
+#define YUV_INPUT_422 (0x00 << 7)
+#define YUV_INPUT_420 (0x01 << 7)
+#define INPUT_420_ORDER_EVEN (0x00 << 8)
+#define INPUT_420_ORDER_ODD (0x01 << 8)
+#define CCIR_INPUT_ORDER_ODD (0x00 << 9)
+#define CCIR_INPUT_ORDER_EVEN (0x01 << 9)
+#define RAW_DATA_WIDTH_8 (0x00 << 11)
+#define RAW_DATA_WIDTH_10 (0x01 << 11)
+#define RAW_DATA_WIDTH_12 (0x02 << 11)
+#define YUV_OUTPUT_422 (0x00 << 16)
+#define YUV_OUTPUT_420 (0x01 << 16)
+#define OUTPUT_420_ORDER_EVEN (0x00 << 17)
+#define OUTPUT_420_ORDER_ODD (0x01 << 17)
+#define RAWD_DATA_LITTLE_ENDIAN (0x00 << 18)
+#define RAWD_DATA_BIG_ENDIAN (0x01 << 18)
+#define UV_STORAGE_ORDER_UVUV (0x00 << 19)
+#define UV_STORAGE_ORDER_VUVU (0x01 << 19)
+#define BT1120_CLOCK_SINGLE_EDGES (0x00 << 24)
+#define BT1120_CLOCK_DOUBLE_EDGES (0x01 << 24)
+#define BT1120_TRANSMIT_INTERFACE (0x00 << 25)
+#define BT1120_TRANSMIT_PROGRESS (0x01 << 25)
+#define BT1120_YC_SWAP (0x01 << 26)
+
+/* CIF_SCL_CTRL */
+#define ENABLE_SCL_DOWN (0x01 << 0)
+#define DISABLE_SCL_DOWN (0x00 << 0)
+#define ENABLE_SCL_UP (0x01 << 1)
+#define DISABLE_SCL_UP (0x00 << 1)
+#define ENABLE_YUV_16BIT_BYPASS (0x01 << 4)
+#define DISABLE_YUV_16BIT_BYPASS (0x00 << 4)
+#define ENABLE_RAW_16BIT_BYPASS (0x01 << 5)
+#define DISABLE_RAW_16BIT_BYPASS (0x00 << 5)
+#define ENABLE_32BIT_BYPASS (0x01 << 6)
+#define DISABLE_32BIT_BYPASS (0x00 << 6)
+
+/* CIF_INTSTAT */
+#define CIF_F0_READY (0x01 << 0)
+#define CIF_F1_READY (0x01 << 1)
+
+/* CIF CROP */
+#define CIF_CROP_Y_SHIFT 16
+#define CIF_CROP_X_SHIFT 0
+
+/* CIF_CSI_ID_CTRL0 */
+#define CSI_DISABLE_CAPTURE (0x0 << 0)
+#define CSI_ENABLE_CAPTURE (0x1 << 0)
+#define CSI_WRDDR_TYPE_RAW8 (0x0 << 1)
+#define CSI_WRDDR_TYPE_RAW10 (0x1 << 1)
+#define CSI_WRDDR_TYPE_RAW12 (0x2 << 1)
+#define CSI_WRDDR_TYPE_RGB888 (0x3 << 1)
+#define CSI_WRDDR_TYPE_YUV422 (0x4 << 1)
+#define CSI_DISABLE_COMMAND_MODE (0x0 << 4)
+#define CSI_ENABLE_COMMAND_MODE (0x1 << 4)
+#define CSI_DISABLE_CROP (0x0 << 5)
+#define CSI_ENABLE_CROP (0x1 << 5)
+
+/* CIF_CSI_INTEN */
+#define CSI_FRAME0_START_INTEN(id) (0x1 << ((id) * 2))
+#define CSI_FRAME1_START_INTEN(id) (0x1 << ((id) * 2 + 1))
+#define CSI_FRAME0_END_INTEN(id) (0x1 << ((id) * 2 + 8))
+#define CSI_FRAME1_END_INTEN(id) (0x1 << ((id) * 2 + 9))
+#define CSI_DMA_Y_FIFO_OVERFLOW_INTEN (0x1 << 16)
+#define CSI_DMA_UV_FIFO_OVERFLOW_INTEN (0x1 << 17)
+#define CSI_CONFIG_FIFO_OVERFLOW_INTEN (0x1 << 18)
+#define CSI_BANDWIDTH_LACK_INTEN (0x1 << 19)
+#define CSI_RX_FIFO_OVERFLOW_INTEN (0x1 << 20)
+#define CSI_ALL_FRAME_START_INTEN (0xff << 0)
+#define CSI_ALL_FRAME_END_INTEN (0xff << 8)
+#define CSI_ALL_ERROR_INTEN (0x1f << 16)
+
+/* CIF_CSI_INTSTAT */
+#define CSI_FRAME0_START_ID0 (0x1 << 0)
+#define CSI_FRAME1_START_ID0 (0x1 << 1)
+#define CSI_FRAME0_START_ID1 (0x1 << 2)
+#define CSI_FRAME1_START_ID1 (0x1 << 3)
+#define CSI_FRAME0_START_ID2 (0x1 << 4)
+#define CSI_FRAME1_START_ID2 (0x1 << 5)
+#define CSI_FRAME0_START_ID3 (0x1 << 6)
+#define CSI_FRAME1_START_ID3 (0x1 << 7)
+#define CSI_FRAME0_END_ID0 (0x1 << 8)
+#define CSI_FRAME1_END_ID0 (0x1 << 9)
+#define CSI_FRAME0_END_ID1 (0x1 << 10)
+#define CSI_FRAME1_END_ID1 (0x1 << 11)
+#define CSI_FRAME0_END_ID2 (0x1 << 12)
+#define CSI_FRAME1_END_ID2 (0x1 << 13)
+#define CSI_FRAME0_END_ID3 (0x1 << 14)
+#define CSI_FRAME1_END_ID3 (0x1 << 15)
+#define CSI_DMA_Y_FIFO_OVERFLOW (0x1 << 16)
+#define CSI_DMA_UV_FIFO_OVERFLOW (0x1 << 17)
+#define CSI_CONFIG_FIFO_OVERFLOW (0x1 << 18)
+#define CSI_BANDWIDTH_LACK (0x1 << 19)
+#define CSI_RX_FIFO_OVERFLOW (0x1 << 20)
+
+#define CSI_FIFO_OVERFLOW (CSI_DMA_Y_FIFO_OVERFLOW | \
+ CSI_DMA_UV_FIFO_OVERFLOW | \
+ CSI_CONFIG_FIFO_OVERFLOW | \
+ CSI_RX_FIFO_OVERFLOW)
+
+/* CSI Host Registers Define */
+#define CSIHOST_N_LANES 0x04
+#define CSIHOST_PHY_RSTZ 0x0c
+#define CSIHOST_RESETN 0x10
+#define CSIHOST_ERR1 0x20
+#define CSIHOST_ERR2 0x24
+#define CSIHOST_MSK1 0x28
+#define CSIHOST_MSK2 0x2c
+#define CSIHOST_CONTROL 0x40
+
+#define SW_CPHY_EN(x) ((x) << 0)
+#define SW_DSI_EN(x) ((x) << 4)
+#define SW_DATATYPE_FS(x) ((x) << 8)
+#define SW_DATATYPE_FE(x) ((x) << 14)
+#define SW_DATATYPE_LS(x) ((x) << 20)
+#define SW_DATATYPE_LE(x) ((x) << 26)
+
+#endif
--
2.25.4
^ permalink raw reply related
* [PATCH v3 0/6] hwmon: pmbus: adm1266: add support
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
From: Alexandru Tachici <alexandru.tachici@analog.com>
Add PMBus probing driver for the adm1266 Cascadable
Super Sequencer with Margin Control and Fault Recording.
Driver is using the pmbus_core, creating sysfs files
under hwmon for inputs: vh1->vh4 and vp1->vp13.
1. Add PMBus probing driver for inputs vh1->vh4
and vp1->vp13.
2. Add Block WR command. A pmbus specific implementation
was required because block write with I2C_SMBUS_PROC_CALL
flag allows a maximum of 32 bytes to be received.
3. This makes adm1266 driver expose GPIOs
to user-space. Currently are read only. Future
developments on the firmware will allow
them to be writable.
4. Add debugfs files for go_command and read_state.
5. Blakcboxes are 64 bytes of chip state related data
that is generated on faults. Use the nvmem kernel api
to expose the blackbox chip functionality to userspace.
6. Device tree bindings for ADM1266.
Alexandru Tachici (6):
hwmon: pmbus: adm1266: add support
hwmon: (pmbus/core) Add Block WR
hwmon: pmbus: adm1266: Add support for GPIOs
hwmon: pmbus: adm1266: add debugfs attr for states
hwmon: pmbus: adm1266: read blackbox
dt-bindings: hwmon: Add bindings for ADM1266
Changelog v2 -> v3:
- added block write-read process call in pmbus-core
- expose GPIOs/PDIOs to user-space as readonly
- allow the user to issue commands to adm1266
in go_command debugfs file
- allow the user to read state of the adm1266 sequencer
from debugfs file read_state
- chip generated blackboxes can now be read
from the nvmem file
.../bindings/hwmon/adi,adm1266.yaml | 56 ++
Documentation/hwmon/adm1266.rst | 35 ++
drivers/hwmon/pmbus/Kconfig | 11 +-
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/adm1266.c | 500 ++++++++++++++++++
drivers/hwmon/pmbus/pmbus.h | 4 +
drivers/hwmon/pmbus/pmbus_core.c | 88 +++
7 files changed, 694 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml
create mode 100644 Documentation/hwmon/adm1266.rst
create mode 100644 drivers/hwmon/pmbus/adm1266.c
--
2.20.1
^ permalink raw reply
* [PATCH v3 1/6] hwmon: pmbus: adm1266: add support
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
In-Reply-To: <20200529130506.73511-1-alexandru.tachici@analog.com>
From: Alexandru Tachici <alexandru.tachici@analog.com>
Add pmbus probing driver for the adm1266 Cascadable
Super Sequencer with Margin Control and Fault Recording.
Driver is using the pmbus_core, creating sysfs files
under hwmon for inputs: vh1->vh4 and vp1->vp13.
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
Documentation/hwmon/adm1266.rst | 35 +++++++++++++++++++
drivers/hwmon/pmbus/Kconfig | 9 +++++
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/adm1266.c | 62 +++++++++++++++++++++++++++++++++
4 files changed, 107 insertions(+)
create mode 100644 Documentation/hwmon/adm1266.rst
create mode 100644 drivers/hwmon/pmbus/adm1266.c
diff --git a/Documentation/hwmon/adm1266.rst b/Documentation/hwmon/adm1266.rst
new file mode 100644
index 000000000000..65662115750c
--- /dev/null
+++ b/Documentation/hwmon/adm1266.rst
@@ -0,0 +1,35 @@
+Kernel driver adm1266
+=====================
+
+Supported chips:
+ * Analog Devices ADM1266
+ Prefix: 'adm1266'
+ Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1266.pdf
+
+Author: Alexandru Tachici <alexandru.tachici@analog.com>
+
+
+Description
+-----------
+
+This driver supports hardware monitoring for Analog Devices ADM1266 sequencer.
+
+ADM1266 is a sequencer that features voltage readback from 17 channels via an
+integrated 12 bit SAR ADC, accessed using a PMBus interface.
+
+The driver is a client driver to the core PMBus driver. Please see
+Documentation/hwmon/pmbus for details on PMBus client drivers.
+
+
+Sysfs entries
+-------------
+
+The following attributes are supported. Limits are read-write, history reset
+attributes are write-only, all other attributes are read-only.
+
+inX_label "voutx"
+inX_input Measured voltage.
+inX_min Minimum Voltage.
+inX_max Maximum voltage.
+inX_min_alarm Voltage low alarm.
+inX_max_alarm Voltage high alarm.
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index de12a565006d..6949483aa732 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -26,6 +26,15 @@ config SENSORS_PMBUS
This driver can also be built as a module. If so, the module will
be called pmbus.
+config SENSORS_ADM1266
+ tristate "Analog Devices ADM1266 Sequencer"
+ help
+ If you say yes here you get hardware monitoring support for Analog
+ Devices ADM1266 Cascadable Super Sequencer.
+
+ This driver can also be built as a module. If so, the module will
+ be called adm1266.
+
config SENSORS_ADM1275
tristate "Analog Devices ADM1275 and compatibles"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 5feb45806123..ed38f6d6f845 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -5,6 +5,7 @@
obj-$(CONFIG_PMBUS) += pmbus_core.o
obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
+obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
new file mode 100644
index 000000000000..a7ef048a9a5c
--- /dev/null
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ADM1266 - Cascadable Super Sequencer with Margin
+ * Control and Fault Recording
+ *
+ * Copyright 2020 Analog Devices Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "pmbus.h"
+
+static int adm1266_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct pmbus_driver_info *info;
+ u32 funcs;
+ int i;
+
+ info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info),
+ GFP_KERNEL);
+
+ info->pages = 17;
+ info->format[PSC_VOLTAGE_OUT] = linear;
+ funcs = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
+ for (i = 0; i < info->pages; i++)
+ info->func[i] = funcs;
+
+ return pmbus_do_probe(client, id, info);
+}
+
+static const struct of_device_id adm1266_of_match[] = {
+ { .compatible = "adi,adm1266" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adm1266_of_match);
+
+static const struct i2c_device_id adm1266_id[] = {
+ { "adm1266", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adm1266_id);
+
+static struct i2c_driver adm1266_driver = {
+ .driver = {
+ .name = "adm1266",
+ .of_match_table = adm1266_of_match,
+ },
+ .probe = adm1266_probe,
+ .remove = pmbus_do_remove,
+ .id_table = adm1266_id,
+};
+
+module_i2c_driver(adm1266_driver);
+
+MODULE_AUTHOR("Alexandru Tachici <alexandru.tachici@analog.com>");
+MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1266");
+MODULE_LICENSE("GPL v2");
--
2.20.1
^ permalink raw reply related
* [PATCH v3 2/6] hwmon: (pmbus/core) Add Block WR
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
In-Reply-To: <20200529130506.73511-1-alexandru.tachici@analog.com>
From: Alexandru Tachici <alexandru.tachici@analog.com>
PmBus devices support Block Write-Block Read Process
Call described in SMBus specification v 2.0 with the
exception that Block writes and reads are permitted to
have up 255 data bytes instead of max 32 bytes (SMBus).
This patch adds Block WR process call support for PMBus.
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
drivers/hwmon/pmbus/Kconfig | 2 +-
drivers/hwmon/pmbus/pmbus.h | 4 ++
drivers/hwmon/pmbus/pmbus_core.c | 88 ++++++++++++++++++++++++++++++++
3 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 6949483aa732..f11712fdcea8 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -5,7 +5,7 @@
menuconfig PMBUS
tristate "PMBus support"
- depends on I2C
+ depends on I2C && CRC8
help
Say yes here if you want to enable PMBus support.
diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index 18e06fc6c53f..ae4e15c5dff2 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -392,6 +392,8 @@ enum pmbus_sensor_classes {
#define PMBUS_PHASE_VIRTUAL BIT(30) /* Phases on this page are virtual */
#define PMBUS_PAGE_VIRTUAL BIT(31) /* Page is virtual */
+#define PMBUS_BLOCK_MAX 255
+
enum pmbus_data_format { linear = 0, direct, vid };
enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv };
@@ -467,6 +469,8 @@ int pmbus_read_word_data(struct i2c_client *client, int page, int phase,
u8 reg);
int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg,
u16 word);
+int pmbus_block_wr(struct i2c_client *client, u8 cmd, u8 w_len, u8 *data_w,
+ u8 *data_r);
int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg);
int pmbus_write_byte(struct i2c_client *client, int page, u8 value);
int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg,
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 8d321bf7d15b..ef63468da3b5 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -6,6 +6,7 @@
* Copyright (c) 2012 Guenter Roeck
*/
+#include <linux/crc8.h>
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/math64.h>
@@ -44,6 +45,8 @@
#define PMBUS_NAME_SIZE 24
+DECLARE_CRC8_TABLE(pmbus_crc_table);
+
struct pmbus_sensor {
struct pmbus_sensor *next;
char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */
@@ -184,6 +187,89 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase)
}
EXPORT_SYMBOL_GPL(pmbus_set_page);
+/* Block Write/Read command.
+ * @client: Handle to slave device
+ * @cmd: Byte interpreted by slave
+ * @w_len: Size of write data block; PMBus allows at most 255 bytes
+ * @data_w: byte array which will be written.
+ * @data_r: Byte array into which data will be read; big enough to hold
+ * the data returned by the slave. PMBus allows at most 255 bytes.
+ *
+ * Different from Block Read as it sends data and waits for the slave to
+ * return a value dependent on that data. The protocol is simply a Write Block
+ * followed by a Read Block without the Read-Block command field and the
+ * Write-Block STOP bit.
+ *
+ * Returns number of bytes read or negative errno.
+ */
+int pmbus_block_wr(struct i2c_client *client, u8 cmd, u8 w_len,
+ u8 *data_w, u8 *data_r)
+{
+ u8 write_buf[PMBUS_BLOCK_MAX + 1];
+ struct i2c_msg msgs[2] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = write_buf,
+ .len = w_len + 2,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = PMBUS_BLOCK_MAX,
+ }
+ };
+ u8 addr = 0;
+ u8 crc = 0;
+ int ret;
+
+ msgs[0].buf[0] = cmd;
+ msgs[0].buf[1] = w_len;
+ memcpy(&msgs[0].buf[2], data_w, w_len);
+
+ msgs[0].buf = i2c_get_dma_safe_msg_buf(&msgs[0], 1);
+ if (!msgs[0].buf)
+ return -ENOMEM;
+
+ msgs[1].buf = i2c_get_dma_safe_msg_buf(&msgs[1], 1);
+ if (!msgs[1].buf) {
+ i2c_put_dma_safe_msg_buf(msgs[0].buf, &msgs[0], false);
+ return -ENOMEM;
+ }
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ if (ret != 2) {
+ dev_err(&client->dev, "I2C transfer error.");
+ goto cleanup;
+ }
+
+ if (client->flags & I2C_CLIENT_PEC) {
+ addr = i2c_8bit_addr_from_msg(&msgs[0]);
+ crc = crc8(pmbus_crc_table, &addr, 1, crc);
+ crc = crc8(pmbus_crc_table, msgs[0].buf, msgs[0].len, crc);
+
+ addr = i2c_8bit_addr_from_msg(&msgs[1]);
+ crc = crc8(pmbus_crc_table, &addr, 1, crc);
+ crc = crc8(pmbus_crc_table, msgs[1].buf, msgs[1].buf[0] + 1,
+ crc);
+
+ if (crc != msgs[1].buf[msgs[1].buf[0] + 1]) {
+ ret = -EBADMSG;
+ goto cleanup;
+ }
+ }
+
+ memcpy(data_r, &msgs[1].buf[1], msgs[1].buf[0]);
+ ret = msgs[1].buf[0];
+
+cleanup:
+ i2c_put_dma_safe_msg_buf(msgs[0].buf, &msgs[0], true);
+ i2c_put_dma_safe_msg_buf(msgs[1].buf, &msgs[1], true);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pmbus_block_wr);
+
int pmbus_write_byte(struct i2c_client *client, int page, u8 value)
{
int rv;
@@ -2522,6 +2608,8 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
if (!data->groups)
return -ENOMEM;
+ crc8_populate_msb(pmbus_crc_table, 0x7);
+
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
data->dev = dev;
--
2.20.1
^ permalink raw reply related
* [PATCH v3 6/6] dt-bindings: hwmon: Add bindings for ADM1266
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
In-Reply-To: <20200529130506.73511-1-alexandru.tachici@analog.com>
From: Alexandru Tachici <alexandru.tachici@analog.com>
Add bindings for the Analog Devices ADM1266 sequencer.
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
.../bindings/hwmon/adi,adm1266.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml
diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml
new file mode 100644
index 000000000000..76b62be48d56
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/adi,adm1266.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/adi,adm1266.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADM1266 Cascadable Super Sequencer with Margin
+ Control and Fault Recording
+
+maintainers:
+ - Alexandru Tachici <alexandru.tachici@analog.com>
+
+description: |
+ Analog Devices ADM1266 Cascadable Super Sequencer with Margin
+ Control and Fault Recording.
+ https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1266.pdf
+
+properties:
+ compatible:
+ enum:
+ - adi,adm1266
+
+ reg:
+ description: |
+ I2C address of slave device.
+ items:
+ minimum: 0x40
+ maximum: 0x4F
+
+ avcc-supply:
+ description: |
+ Phandle to the Avcc power supply.
+
+ adi,master-adm1266:
+ description: |
+ Represents phandle of a master ADM1266 device cascaded through the IDB.
+ $ref: "/schemas/types.yaml#/definitions/phandle"
+
+required:
+ - compatible
+ - reg
+
+examples:
+ - |
+ i2c0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adm1266@40 {
+ compatible = "adi,adm1266";
+ reg = <0x40>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+...
--
2.20.1
^ permalink raw reply related
* [PATCH v3 5/6] hwmon: pmbus: adm1266: read blackbox
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
In-Reply-To: <20200529130506.73511-1-alexandru.tachici@analog.com>
From: Alexandru Tachici <alexandru.tachici@analog.com>
Use the nvmem kernel api to expose the black box
chip functionality to userspace.
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
drivers/hwmon/pmbus/adm1266.c | 160 ++++++++++++++++++++++++++++++++++
1 file changed, 160 insertions(+)
diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index 85d6795b79d3..831156004087 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -14,14 +14,19 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
#include <linux/slab.h>
#include "pmbus.h"
+#define ADM1266_BLACKBOX_CONFIG 0xD3
#define ADM1266_PDIO_CONFIG 0xD4
#define ADM1266_GO_COMMAND 0xD8
#define ADM1266_READ_STATE 0xD9
+#define ADM1266_READ_BLACKBOX 0xDE
#define ADM1266_GPIO_CONFIG 0xE1
+#define ADM1266_BLACKBOX_INFO 0xE6
#define ADM1266_PDIO_STATUS 0xE9
#define ADM1266_GPIO_STATUS 0xEA
@@ -38,12 +43,26 @@
#define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x)
#define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x)
+#define ADM1266_BLACKBOX_OFFSET 0x7F700
+#define ADM1266_BLACKBOX_SIZE 64
+
struct adm1266_data {
struct pmbus_driver_info info;
struct gpio_chip gc;
const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
struct i2c_client *client;
struct dentry *debugfs_dir;
+ struct nvmem_config nvmem_config;
+ struct nvmem_device *nvmem;
+ u8 *dev_mem;
+};
+
+static const struct nvmem_cell_info adm1266_nvmem_cells[] = {
+ {
+ .name = "blackbox",
+ .offset = ADM1266_BLACKBOX_OFFSET,
+ .bytes = 2048,
+ },
};
#if IS_ENABLED(CONFIG_GPIOLIB)
@@ -261,6 +280,28 @@ static int adm1266_set_go_command_op(void *pdata, u64 val)
return i2c_smbus_write_word_data(data->client, ADM1266_GO_COMMAND, reg);
}
+static int adm1266_blackbox_information_read(struct seq_file *s, void *pdata)
+{
+ struct device *dev = s->private;
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 read_buf[PMBUS_BLOCK_MAX + 1];
+ unsigned int latest_id;
+ int ret;
+
+ ret = i2c_smbus_read_block_data(client, ADM1266_BLACKBOX_INFO,
+ read_buf);
+ if (ret < 0)
+ return ret;
+
+ seq_puts(s, "BLACKBOX_INFORMATION:\n");
+ latest_id = read_buf[0] + (read_buf[1] << 8);
+ seq_printf(s, "Black box ID: %x\n", latest_id);
+ seq_printf(s, "Logic index: %x\n", read_buf[2]);
+ seq_printf(s, "Record count: %x\n", read_buf[3]);
+
+ return 0;
+}
+
DEFINE_DEBUGFS_ATTRIBUTE(go_command_fops, NULL, adm1266_set_go_command_op,
"%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(read_state_fops, adm1266_get_state_op, NULL, "%llu\n");
@@ -277,6 +318,121 @@ static void adm1266_debug_init(struct adm1266_data *data)
&go_command_fops);
debugfs_create_file_unsafe("read_state", 0400, root, data,
&read_state_fops);
+ debugfs_create_devm_seqfile(&data->client->dev, "blackbox_information",
+ root, adm1266_blackbox_information_read);
+}
+
+static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *buf)
+{
+ u8 write_buf[PMBUS_BLOCK_MAX + 1];
+ u8 read_buf[PMBUS_BLOCK_MAX + 1];
+ int record_count;
+ int ret;
+ int i;
+
+ ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO,
+ read_buf);
+ if (ret < 0)
+ return ret;
+
+ record_count = read_buf[3];
+
+ for (i = 0; i < record_count; i++) {
+ write_buf[0] = i;
+ ret = pmbus_block_wr(data->client, ADM1266_READ_BLACKBOX, 1,
+ write_buf, buf);
+ if (ret < 0)
+ return ret;
+
+ buf += ADM1266_BLACKBOX_SIZE;
+ }
+
+ return 0;
+}
+
+static bool adm1266_cell_is_accessed(const struct nvmem_cell_info *mem_cell,
+ unsigned int offset, size_t bytes)
+{
+ unsigned int start_addr = offset;
+ unsigned int end_addr = offset + bytes;
+ unsigned int cell_start = mem_cell->offset;
+ unsigned int cell_end = mem_cell->offset + mem_cell->bytes;
+
+ if (start_addr <= cell_end && cell_start <= end_addr)
+ return true;
+
+ return false;
+}
+
+static int adm1266_read_mem_cell(struct adm1266_data *data,
+ const struct nvmem_cell_info *mem_cell)
+{
+ u8 *mem_offset;
+ int ret;
+
+ switch (mem_cell->offset) {
+ case ADM1266_BLACKBOX_OFFSET:
+ mem_offset = data->dev_mem + mem_cell->offset;
+ ret = adm1266_nvmem_read_blackbox(data, mem_offset);
+ if (ret)
+ dev_err(&data->client->dev, "Could not read blackbox!");
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ const struct nvmem_cell_info *mem_cell;
+ struct adm1266_data *data = priv;
+ int ret;
+ int i;
+
+ for (i = 0; i < data->nvmem_config.ncells; i++) {
+ mem_cell = &adm1266_nvmem_cells[i];
+ if (!adm1266_cell_is_accessed(mem_cell, offset, bytes))
+ continue;
+
+ ret = adm1266_read_mem_cell(data, mem_cell);
+ if (ret < 0)
+ return ret;
+ }
+
+ memcpy(val, data->dev_mem + offset, bytes);
+
+ return 0;
+}
+
+static int adm1266_config_nvmem(struct adm1266_data *data)
+{
+ data->nvmem_config.name = dev_name(&data->client->dev);
+ data->nvmem_config.dev = &data->client->dev;
+ data->nvmem_config.root_only = true;
+ data->nvmem_config.read_only = true;
+ data->nvmem_config.owner = THIS_MODULE;
+ data->nvmem_config.reg_read = adm1266_nvmem_read;
+ data->nvmem_config.cells = adm1266_nvmem_cells;
+ data->nvmem_config.ncells = ARRAY_SIZE(adm1266_nvmem_cells);
+ data->nvmem_config.priv = data;
+ data->nvmem_config.stride = 1;
+ data->nvmem_config.word_size = 1;
+ data->nvmem_config.size = 0x80000;
+
+ data->nvmem = nvmem_register(&data->nvmem_config);
+ if (IS_ERR(data->nvmem)) {
+ dev_err(&data->client->dev, "Could not register nvmem!");
+ return PTR_ERR(data->nvmem);
+ }
+
+ data->dev_mem = devm_kzalloc(&data->client->dev,
+ data->nvmem_config.size,
+ GFP_KERNEL);
+ if (!data->dev_mem)
+ return -ENOMEM;
+
+ return 0;
}
static int adm1266_probe(struct i2c_client *client,
@@ -299,6 +455,10 @@ static int adm1266_probe(struct i2c_client *client,
if (ret < 0)
return ret;
+ ret = adm1266_config_nvmem(data);
+ if (ret < 0)
+ return ret;
+
adm1266_debug_init(data);
info = &data->info;
--
2.20.1
^ permalink raw reply related
* [PATCH v3 4/6] hwmon: pmbus: adm1266: add debugfs attr for states
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
In-Reply-To: <20200529130506.73511-1-alexandru.tachici@analog.com>
From: Alexandru Tachici <alexandru.tachici@analog.com>
Add debugfs files for go_command and read_state.
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
drivers/hwmon/pmbus/adm1266.c | 47 +++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index 190170300ef1..85d6795b79d3 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -19,6 +19,8 @@
#include "pmbus.h"
#define ADM1266_PDIO_CONFIG 0xD4
+#define ADM1266_GO_COMMAND 0xD8
+#define ADM1266_READ_STATE 0xD9
#define ADM1266_GPIO_CONFIG 0xE1
#define ADM1266_PDIO_STATUS 0xE9
#define ADM1266_GPIO_STATUS 0xEA
@@ -41,6 +43,7 @@ struct adm1266_data {
struct gpio_chip gc;
const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
struct i2c_client *client;
+ struct dentry *debugfs_dir;
};
#if IS_ENABLED(CONFIG_GPIOLIB)
@@ -234,6 +237,48 @@ static inline int adm1266_config_gpio(struct adm1266_data *data)
}
#endif
+static int adm1266_get_state_op(void *pdata, u64 *state)
+{
+ struct adm1266_data *data = pdata;
+ int ret;
+
+ ret = i2c_smbus_read_word_data(data->client, ADM1266_READ_STATE);
+ if (ret < 0)
+ return ret;
+
+ *state = ret;
+
+ return 0;
+}
+
+static int adm1266_set_go_command_op(void *pdata, u64 val)
+{
+ struct adm1266_data *data = pdata;
+ u8 reg;
+
+ reg = FIELD_GET(GENMASK(4, 0), val);
+
+ return i2c_smbus_write_word_data(data->client, ADM1266_GO_COMMAND, reg);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(go_command_fops, NULL, adm1266_set_go_command_op,
+ "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(read_state_fops, adm1266_get_state_op, NULL, "%llu\n");
+
+static void adm1266_debug_init(struct adm1266_data *data)
+{
+ struct dentry *root;
+ char dir_name[30];
+
+ sprintf(dir_name, "adm1266-%x_debugfs", data->client->addr);
+ root = debugfs_create_dir(dir_name, NULL);
+ data->debugfs_dir = root;
+ debugfs_create_file_unsafe("go_command", 0200, root, data,
+ &go_command_fops);
+ debugfs_create_file_unsafe("read_state", 0400, root, data,
+ &read_state_fops);
+}
+
static int adm1266_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -254,6 +299,8 @@ static int adm1266_probe(struct i2c_client *client,
if (ret < 0)
return ret;
+ adm1266_debug_init(data);
+
info = &data->info;
info->pages = 17;
info->format[PSC_VOLTAGE_OUT] = linear;
--
2.20.1
^ permalink raw reply related
* [PATCH v3 3/6] hwmon: pmbus: adm1266: Add support for GPIOs
From: alexandru.tachici @ 2020-05-29 13:05 UTC (permalink / raw)
To: linux-hwmon, linux-kernel, devicetree; +Cc: robh+dt, linux, Alexandru Tachici
In-Reply-To: <20200529130506.73511-1-alexandru.tachici@analog.com>
From: Alexandru Tachici <alexandru.tachici@analog.com>
Adm1266 exposes 9 GPIOs and 16 PDIOs which are currently read-only. They
are controlled by the internal sequencing engine.
This patch makes adm1266 driver expose GPIOs and PDIOs to user-space
using GPIO provider kernel api.
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
---
drivers/hwmon/pmbus/adm1266.c | 233 +++++++++++++++++++++++++++++++++-
1 file changed, 232 insertions(+), 1 deletion(-)
diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index a7ef048a9a5c..190170300ef1 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -6,7 +6,11 @@
* Copyright 2020 Analog Devices Inc.
*/
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/gpio/driver.h>
#include <linux/i2c.h>
+#include <linux/i2c-smbus.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -14,16 +18,243 @@
#include "pmbus.h"
+#define ADM1266_PDIO_CONFIG 0xD4
+#define ADM1266_GPIO_CONFIG 0xE1
+#define ADM1266_PDIO_STATUS 0xE9
+#define ADM1266_GPIO_STATUS 0xEA
+
+/* ADM1266 GPIO defines */
+#define ADM1266_GPIO_NR 9
+#define ADM1266_GPIO_FUNCTIONS(x) FIELD_GET(BIT(0), x)
+#define ADM1266_GPIO_INPUT_EN(x) FIELD_GET(BIT(2), x)
+#define ADM1266_GPIO_OUTPUT_EN(x) FIELD_GET(BIT(3), x)
+#define ADM1266_GPIO_OPEN_DRAIN(x) FIELD_GET(BIT(4), x)
+
+/* ADM1266 PDIO defines */
+#define ADM1266_PDIO_NR 16
+#define ADM1266_PDIO_PIN_CFG(x) FIELD_GET(GENMASK(15, 13), x)
+#define ADM1266_PDIO_GLITCH_FILT(x) FIELD_GET(GENMASK(12, 9), x)
+#define ADM1266_PDIO_OUT_CFG(x) FIELD_GET(GENMASK(2, 0), x)
+
+struct adm1266_data {
+ struct pmbus_driver_info info;
+ struct gpio_chip gc;
+ const char *gpio_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR];
+ struct i2c_client *client;
+};
+
+#if IS_ENABLED(CONFIG_GPIOLIB)
+static const unsigned int adm1266_gpio_mapping[ADM1266_GPIO_NR][2] = {
+ {1, 0},
+ {2, 1},
+ {3, 2},
+ {4, 8},
+ {5, 9},
+ {6, 10},
+ {7, 11},
+ {8, 6},
+ {9, 7},
+};
+
+static const char *adm1266_names[ADM1266_GPIO_NR + ADM1266_PDIO_NR] = {
+ "GPIO1", "GPIO2", "GPIO3", "GPIO4", "GPIO5", "GPIO6", "GPIO7", "GPIO8",
+ "GPIO9", "PDIO1", "PDIO2", "PDIO3", "PDIO4", "PDIO5", "PDIO6",
+ "PDIO7", "PDIO8", "PDIO9", "PDIO10", "PDIO11", "PDIO12", "PDIO13",
+ "PDIO14", "PDIO15", "PDIO16",
+};
+
+static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct adm1266_data *data = gpiochip_get_data(chip);
+ u8 read_buf[PMBUS_BLOCK_MAX + 1];
+ unsigned long pins_status;
+ unsigned int pmbus_cmd;
+ int ret;
+
+ if (offset < ADM1266_GPIO_NR)
+ pmbus_cmd = ADM1266_GPIO_STATUS;
+ else
+ pmbus_cmd = ADM1266_PDIO_STATUS;
+
+ ret = i2c_smbus_read_block_data(data->client, pmbus_cmd,
+ read_buf);
+ if (ret < 0)
+ return ret;
+
+ pins_status = read_buf[0] + (read_buf[1] << 8);
+ if (offset < ADM1266_GPIO_NR)
+ return test_bit(adm1266_gpio_mapping[offset][1], &pins_status);
+ else
+ return test_bit(offset - ADM1266_GPIO_NR, &pins_status);
+}
+
+static int adm1266_gpio_get_multiple(struct gpio_chip *chip,
+ unsigned long *mask,
+ unsigned long *bits)
+{
+ struct adm1266_data *data = gpiochip_get_data(chip);
+ u8 gpio_data[PMBUS_BLOCK_MAX + 1];
+ u8 pdio_data[PMBUS_BLOCK_MAX + 1];
+ unsigned long gpio_status;
+ unsigned long pdio_status;
+ unsigned int gpio_nr;
+ int ret;
+
+ ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS,
+ gpio_data);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS,
+ pdio_data);
+ if (ret < 0)
+ return ret;
+
+ gpio_status = gpio_data[0] + (gpio_data[1] << 8);
+ pdio_status = pdio_data[0] + (pdio_data[1] << 8);
+ *bits = 0;
+ for_each_set_bit(gpio_nr, mask, ADM1266_GPIO_NR) {
+ if (test_bit(adm1266_gpio_mapping[gpio_nr][1], &gpio_status))
+ set_bit(gpio_nr, bits);
+ }
+
+ for_each_set_bit_from(gpio_nr, mask,
+ ADM1266_GPIO_NR + ADM1266_PDIO_STATUS) {
+ if (test_bit(gpio_nr - ADM1266_GPIO_NR, &pdio_status))
+ set_bit(gpio_nr, bits);
+ }
+
+ return 0;
+}
+
+static void adm1266_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct adm1266_data *data = gpiochip_get_data(chip);
+ u8 write_buf[PMBUS_BLOCK_MAX + 1];
+ u8 read_buf[PMBUS_BLOCK_MAX + 1];
+ unsigned long gpio_config;
+ unsigned long pdio_config;
+ unsigned long pin_cfg;
+ int ret;
+ int i;
+
+ for (i = 0; i < ADM1266_GPIO_NR; i++) {
+ write_buf[0] = adm1266_gpio_mapping[i][1];
+ ret = pmbus_block_wr(data->client, ADM1266_GPIO_CONFIG, 1,
+ write_buf, read_buf);
+ if (ret < 0)
+ dev_err(&data->client->dev, "GPIOs scan failed(%d).\n",
+ ret);
+
+ gpio_config = read_buf[0];
+ seq_puts(s, adm1266_names[i]);
+
+ seq_puts(s, " ( ");
+ if (!ADM1266_GPIO_FUNCTIONS(gpio_config)) {
+ seq_puts(s, "high-Z )\n");
+ continue;
+ }
+ if (ADM1266_GPIO_INPUT_EN(gpio_config))
+ seq_puts(s, "input ");
+ if (ADM1266_GPIO_OUTPUT_EN(gpio_config))
+ seq_puts(s, "output ");
+ if (ADM1266_GPIO_OPEN_DRAIN(gpio_config))
+ seq_puts(s, "open-drain )\n");
+ else
+ seq_puts(s, "push-pull )\n");
+ }
+
+ write_buf[0] = 0xFF;
+ ret = pmbus_block_wr(data->client, ADM1266_PDIO_CONFIG, 1, write_buf,
+ read_buf);
+ if (ret < 0)
+ dev_err(&data->client->dev, "PDIOs scan failed(%d).\n", ret);
+
+ for (i = 0; i < ADM1266_PDIO_NR; i++) {
+ seq_puts(s, adm1266_names[ADM1266_GPIO_NR + i]);
+
+ pdio_config = read_buf[2 * i];
+ pdio_config += (read_buf[2 * i + 1] << 8);
+ pin_cfg = ADM1266_PDIO_PIN_CFG(pdio_config);
+
+ seq_puts(s, " ( ");
+ if (!pin_cfg || pin_cfg > 5) {
+ seq_puts(s, "high-Z )\n");
+ continue;
+ }
+
+ if (pin_cfg & BIT(0))
+ seq_puts(s, "output ");
+
+ if (pin_cfg & BIT(1))
+ seq_puts(s, "input ");
+
+ seq_puts(s, ")\n");
+ }
+}
+
+static int adm1266_config_gpio(struct adm1266_data *data)
+{
+ const char *name = dev_name(&data->client->dev);
+ char *gpio_name;
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(data->gpio_names); i++) {
+ gpio_name = devm_kasprintf(&data->client->dev, GFP_KERNEL,
+ "adm1266-%x-%s", data->client->addr,
+ adm1266_names[i]);
+ if (!gpio_name)
+ return -ENOMEM;
+
+ data->gpio_names[i] = gpio_name;
+ }
+
+ data->gc.label = name;
+ data->gc.parent = &data->client->dev;
+ data->gc.owner = THIS_MODULE;
+ data->gc.base = -1;
+ data->gc.names = data->gpio_names;
+ data->gc.ngpio = ARRAY_SIZE(data->gpio_names);
+ data->gc.get = adm1266_gpio_get;
+ data->gc.get_multiple = adm1266_gpio_get_multiple;
+ data->gc.dbg_show = adm1266_gpio_dbg_show;
+
+ ret = devm_gpiochip_add_data(&data->client->dev, &data->gc, data);
+ if (ret)
+ dev_err(&data->client->dev, "GPIO registering failed (%d)\n",
+ ret);
+
+ return ret;
+}
+#else
+static inline int adm1266_config_gpio(struct adm1266_data *data)
+{
+ return 0;
+}
+#endif
+
static int adm1266_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct pmbus_driver_info *info;
+ struct adm1266_data *data;
u32 funcs;
+ int ret;
int i;
- info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info),
+ data = devm_kzalloc(&client->dev, sizeof(struct adm1266_data),
GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+
+ ret = adm1266_config_gpio(data);
+ if (ret < 0)
+ return ret;
+ info = &data->info;
info->pages = 17;
info->format[PSC_VOLTAGE_OUT] = linear;
funcs = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
--
2.20.1
^ permalink raw reply related
* Re: [PATCH 3/4] pinctrl: bcm2835: Match BCM7211 compatible string
From: Stefan Wahren @ 2020-05-29 13:11 UTC (permalink / raw)
To: Florian Fainelli, linux-kernel
Cc: Linus Walleij, Rob Herring, Ray Jui, Scott Branden,
maintainer:BROADCOM BCM281XX/BCM11XXX/BCM216XX ARM ARCHITE...,
Nicolas Saenz Julienne, Geert Uytterhoeven, Matti Vaittinen,
open list:PIN CONTROL SUBSYSTEM,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
moderated list:BROADCOM BCM2711/BCM2835 ARM ARCHITECTURE,
moderated list:BROADCOM BCM2711/BCM2835 ARM ARCHITECTURE
In-Reply-To: <20200528192112.26123-4-f.fainelli@gmail.com>
Hi Florian,
Am 28.05.20 um 21:21 schrieb Florian Fainelli:
> The BCM7211 SoC uses the same pinconf_ops as the ones defined for the
> BCM2711 SoC, match the compatible string and use the correct set of
> options.
>
> Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
> ---
> drivers/pinctrl/bcm/pinctrl-bcm2835.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2835.c b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
> index 06bd2b70af3c..e8ad1824c6b3 100644
> --- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c
> +++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
> @@ -1137,6 +1137,10 @@ static const struct of_device_id bcm2835_pinctrl_match[] = {
> .compatible = "brcm,bcm2711-gpio",
> .data = &bcm2711_plat_data,
> },
> + {
> + .compatible = "brcm,bcm7211-gpio",
> + .data = &bcm2711_pinconf_ops,
this doesn't look safe. Maybe bcm2711_plat_data?
Looks like the original patch series based on a older version. Please
double check this still applies since the introduction of 58 GPIO
support for BCM2711.
Regards
Stefan
> + },
> {}
> };
>
^ permalink raw reply
* [PATCH v6 02/16] spi: dw: Return any value retrieved from the dma_transfer callback
From: Serge Semin @ 2020-05-29 13:11 UTC (permalink / raw)
To: Mark Brown
Cc: Serge Semin, Serge Semin, Georgy Vlasov, Ramil Zaripov,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann,
Andy Shevchenko, Feng Tang, Rob Herring, linux-mips, devicetree,
linux-spi, linux-kernel
In-Reply-To: <20200529131205.31838-1-Sergey.Semin@baikalelectronics.ru>
DW APB SSI DMA-part of the driver may need to perform the requested
SPI-transfer synchronously. In that case the dma_transfer() callback
will return 0 as a marker of the SPI transfer being finished so the
SPI core doesn't need to wait and may proceed with the SPI message
trasnfers pumping procedure. This will be needed to fix the problem
when DMA transactions are finished, but there is still data left in
the SPI Tx/Rx FIFOs being sent/received. But for now make dma_transfer
to return 1 as the normal dw_spi_transfer_one() method.
Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Cc: Georgy Vlasov <Georgy.Vlasov@baikalelectronics.ru>
Cc: Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru>
Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Feng Tang <feng.tang@intel.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: linux-mips@vger.kernel.org
Cc: devicetree@vger.kernel.org
---
drivers/spi/spi-dw-mid.c | 2 +-
drivers/spi/spi-dw.c | 7 ++-----
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c
index b1710132b7b2..7ff1acaa55f8 100644
--- a/drivers/spi/spi-dw-mid.c
+++ b/drivers/spi/spi-dw-mid.c
@@ -288,7 +288,7 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer)
dma_async_issue_pending(dws->txchan);
}
- return 0;
+ return 1;
}
static void mid_spi_dma_stop(struct dw_spi *dws)
diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c
index 050cb2ea0812..6939e003e3e9 100644
--- a/drivers/spi/spi-dw.c
+++ b/drivers/spi/spi-dw.c
@@ -389,11 +389,8 @@ static int dw_spi_transfer_one(struct spi_controller *master,
spi_enable_chip(dws, 1);
- if (dws->dma_mapped) {
- ret = dws->dma_ops->dma_transfer(dws, transfer);
- if (ret < 0)
- return ret;
- }
+ if (dws->dma_mapped)
+ return dws->dma_ops->dma_transfer(dws, transfer);
return 1;
}
--
2.26.2
^ permalink raw reply related
* [PATCH v6 05/16] spi: dw: Add SPI Rx-done wait method to DMA-based transfer
From: Serge Semin @ 2020-05-29 13:11 UTC (permalink / raw)
To: Mark Brown, Feng Tang, Alan Cox, Linus Walleij, Vinod Koul,
Grant Likely
Cc: Serge Semin, Serge Semin, Georgy Vlasov, Ramil Zaripov,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann,
Andy Shevchenko, Rob Herring, linux-mips, devicetree, linux-spi,
linux-kernel
In-Reply-To: <20200529131205.31838-1-Sergey.Semin@baikalelectronics.ru>
Having any data left in the Rx FIFO after the DMA engine claimed it has
finished all DMA transactions is an abnormal situation, since the DW SPI
controller driver expects to have all the data being fetched and placed
into the SPI Rx buffer at that moment. In case if that has happened we
hopefully assume that the DMA engine may still be doing the data fetching,
thus we give it sometime to finish. If after a short period of time the
data is still left in the Rx FIFO, the driver will give up waiting and
return an error indicating that the SPI controller/DMA engine must have
hung up or failed at some point of doing their duties.
Fixes: 7063c0d942a1 ("spi/dw_spi: add DMA support")
Co-developed-by: Georgy Vlasov <Georgy.Vlasov@baikalelectronics.ru>
Signed-off-by: Georgy Vlasov <Georgy.Vlasov@baikalelectronics.ru>
Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Cc: Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru>
Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Feng Tang <feng.tang@intel.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: linux-mips@vger.kernel.org
Cc: devicetree@vger.kernel.org
---
Changelog v5:
- Create a dedicated patch which adds the Rx-done wait method.
- Add more detailed description of the problem the patch fixes.
- Wait for the SPI Rx transfer finish in the mid_spi_dma_transfer() method
executed in the task context.
- Use spi_delay_exec() to wait for the SPI Rx completion, since now the
driver does in the kernel thread context.
- Wait for a delay correlated with the APB/SSI synchronous clock rate
instead of using the SPI bus clock rate.
Changelog v6:
- Calculate the delay with better accuracy by moving 4-multiplication
to the head of the formulae:
ns = 4U * NSEC_PER_SEC / dws->max_freq * nents.
---
drivers/spi/spi-dw-mid.c | 48 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c
index 846e3db91329..abd6955ad1f7 100644
--- a/drivers/spi/spi-dw-mid.c
+++ b/drivers/spi/spi-dw-mid.c
@@ -248,6 +248,49 @@ static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws,
return txdesc;
}
+static inline bool dw_spi_dma_rx_busy(struct dw_spi *dws)
+{
+ return !!(dw_readl(dws, DW_SPI_SR) & SR_RF_NOT_EMPT);
+}
+
+static int dw_spi_dma_wait_rx_done(struct dw_spi *dws)
+{
+ int retry = WAIT_RETRIES;
+ struct spi_delay delay;
+ unsigned long ns, us;
+ u32 nents;
+
+ /*
+ * It's unlikely that DMA engine is still doing the data fetching, but
+ * if it's let's give it some reasonable time. The timeout calculation
+ * is based on the synchronous APB/SSI reference clock rate, on a
+ * number of data entries left in the Rx FIFO, times a number of clock
+ * periods normally needed for a single APB read/write transaction
+ * without PREADY signal utilized (which is true for the DW APB SSI
+ * controller).
+ */
+ nents = dw_readl(dws, DW_SPI_RXFLR);
+ ns = 4U * NSEC_PER_SEC / dws->max_freq * nents;
+ if (ns <= NSEC_PER_USEC) {
+ delay.unit = SPI_DELAY_UNIT_NSECS;
+ delay.value = ns;
+ } else {
+ us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
+ delay.unit = SPI_DELAY_UNIT_USECS;
+ delay.value = clamp_val(us, 0, USHRT_MAX);
+ }
+
+ while (dw_spi_dma_rx_busy(dws) && retry--)
+ spi_delay_exec(&delay, NULL);
+
+ if (retry < 0) {
+ dev_err(&dws->master->dev, "Rx hanged up\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
/*
* dws->dma_chan_busy is set before the dma transfer starts, callback for rx
* channel will clear a corresponding bit.
@@ -358,7 +401,10 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer)
return ret;
}
- return 0;
+ if (rxdesc && dws->master->cur_msg->status == -EINPROGRESS)
+ ret = dw_spi_dma_wait_rx_done(dws);
+
+ return ret;
}
static void mid_spi_dma_stop(struct dw_spi *dws)
--
2.26.2
^ permalink raw reply related
* [PATCH v6 03/16] spi: dw: Locally wait for the DMA transfers completion
From: Serge Semin @ 2020-05-29 13:11 UTC (permalink / raw)
To: Mark Brown
Cc: Serge Semin, Serge Semin, Georgy Vlasov, Ramil Zaripov,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, Feng Tang,
Andy Shevchenko, Rob Herring, linux-mips, devicetree, linux-spi,
linux-kernel
In-Reply-To: <20200529131205.31838-1-Sergey.Semin@baikalelectronics.ru>
In general each DMA-based SPI transfer can be split up into two stages:
DMA data transmission/reception and SPI-bus transmission/reception. DMA
asynchronous transactions completion can be tracked by means of the
DMA async Tx-descriptor completion callback. But that callback being
called indicates that the DMA transfer has been finished, it doesn't
mean that SPI data transmission is also done. Moreover in fact it isn't
for at least Tx-only SPI transfers. Upon DMA transfer completion some
data is left in the Tx FIFO and being pushed out by the SPI controller.
So in order to make sure that an SPI transfer is completely pushed to the
SPI-bus, the driver has to wait for both DMA transaction and the SPI-bus
transmission/reception are finished. Note if there is a way to
asynchronously track the former event by means of the DMA async Tx
callback, there isn't easy one for the later (IRQ-based solution won't
work since SPI controller doesn't notify about Rx FIFO being empty).
The DMA transfer completion callback isn't suitable to wait for the
SPI controller activity finish either. The callback might (in case of DW
DMAC it will) be called in the tasklet context. Waiting for the SPI
controller to complete the transfer might take a considerable amount of
time since SPI-bus might be pretty slow. In this case delaying the
execution in the tasklet atomic context might cause significant system
performance drop.
So to speak the best option we've got to solve the problem is to
consequently wait for both stages being finished in the locally
implemented SPI transfer execution procedure even if it costs us of the
local wait-function re-implementation. In this case we don't need to use
the SPI-core transfer-wait functionality, but we'll make sure that
all DMA and SPI-bus transactions are completely finished before the
SPI-core transfer_one callback returns. In this commit we provide an
implementation of the DMA-transfers completion wait functionality.
The DW APB SSI DMA-specific SPI transfer_one function waits for both
Tx and Rx DMA transfers being finished, and only then exits with zero
returned signalling to the SPI core that the SPI transfer is finished.
This implementation is fully equivalent to the currently used
DMA-execution-SPI-core-wait algorithm. The SPI-bus transmission/reception
wait methods will be added in the follow-up commits.
Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Cc: Georgy Vlasov <Georgy.Vlasov@baikalelectronics.ru>
Cc: Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru>
Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Feng Tang <feng.tang@intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: linux-mips@vger.kernel.org
Cc: devicetree@vger.kernel.org
---
drivers/spi/spi-dw-mid.c | 44 ++++++++++++++++++++++++++++++++++++----
drivers/spi/spi-dw.h | 2 ++
2 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-dw-mid.c b/drivers/spi/spi-dw-mid.c
index 7ff1acaa55f8..355b641c4483 100644
--- a/drivers/spi/spi-dw-mid.c
+++ b/drivers/spi/spi-dw-mid.c
@@ -11,9 +11,11 @@
#include "spi-dw.h"
#ifdef CONFIG_SPI_DW_MID_DMA
+#include <linux/completion.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/irqreturn.h>
+#include <linux/jiffies.h>
#include <linux/pci.h>
#include <linux/platform_data/dma-dw.h>
@@ -66,6 +68,8 @@ static int mid_spi_dma_init_mfld(struct device *dev, struct dw_spi *dws)
dws->master->dma_rx = dws->rxchan;
dws->master->dma_tx = dws->txchan;
+ init_completion(&dws->dma_completion);
+
return 0;
free_rxchan:
@@ -91,6 +95,8 @@ static int mid_spi_dma_init_generic(struct device *dev, struct dw_spi *dws)
dws->master->dma_rx = dws->rxchan;
dws->master->dma_tx = dws->txchan;
+ init_completion(&dws->dma_completion);
+
return 0;
}
@@ -121,7 +127,7 @@ static irqreturn_t dma_transfer(struct dw_spi *dws)
dev_err(&dws->master->dev, "%s: FIFO overrun/underrun\n", __func__);
dws->master->cur_msg->status = -EIO;
- spi_finalize_current_transfer(dws->master);
+ complete(&dws->dma_completion);
return IRQ_HANDLED;
}
@@ -142,6 +148,29 @@ static enum dma_slave_buswidth convert_dma_width(u8 n_bytes) {
return DMA_SLAVE_BUSWIDTH_UNDEFINED;
}
+static int dw_spi_dma_wait(struct dw_spi *dws, struct spi_transfer *xfer)
+{
+ unsigned long long ms;
+
+ ms = xfer->len * MSEC_PER_SEC * BITS_PER_BYTE;
+ do_div(ms, xfer->effective_speed_hz);
+ ms += ms + 200;
+
+ if (ms > UINT_MAX)
+ ms = UINT_MAX;
+
+ ms = wait_for_completion_timeout(&dws->dma_completion,
+ msecs_to_jiffies(ms));
+
+ if (ms == 0) {
+ dev_err(&dws->master->cur_msg->spi->dev,
+ "DMA transaction timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
/*
* dws->dma_chan_busy is set before the dma transfer starts, callback for tx
* channel will clear a corresponding bit.
@@ -155,7 +184,7 @@ static void dw_spi_dma_tx_done(void *arg)
return;
dw_writel(dws, DW_SPI_DMACR, 0);
- spi_finalize_current_transfer(dws->master);
+ complete(&dws->dma_completion);
}
static struct dma_async_tx_descriptor *dw_spi_dma_prepare_tx(struct dw_spi *dws,
@@ -204,7 +233,7 @@ static void dw_spi_dma_rx_done(void *arg)
return;
dw_writel(dws, DW_SPI_DMACR, 0);
- spi_finalize_current_transfer(dws->master);
+ complete(&dws->dma_completion);
}
static struct dma_async_tx_descriptor *dw_spi_dma_prepare_rx(struct dw_spi *dws,
@@ -260,6 +289,8 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer)
/* Set the interrupt mask */
spi_umask_intr(dws, imr);
+ reinit_completion(&dws->dma_completion);
+
dws->transfer_handler = dma_transfer;
return 0;
@@ -268,6 +299,7 @@ static int mid_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer)
static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer)
{
struct dma_async_tx_descriptor *txdesc, *rxdesc;
+ int ret;
/* Prepare the TX dma transfer */
txdesc = dw_spi_dma_prepare_tx(dws, xfer);
@@ -288,7 +320,11 @@ static int mid_spi_dma_transfer(struct dw_spi *dws, struct spi_transfer *xfer)
dma_async_issue_pending(dws->txchan);
}
- return 1;
+ ret = dw_spi_dma_wait(dws, xfer);
+ if (ret)
+ return ret;
+
+ return 0;
}
static void mid_spi_dma_stop(struct dw_spi *dws)
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index 79782e93eb12..9585d0c83a6d 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -2,6 +2,7 @@
#ifndef DW_SPI_HEADER_H
#define DW_SPI_HEADER_H
+#include <linux/completion.h>
#include <linux/irqreturn.h>
#include <linux/io.h>
#include <linux/scatterlist.h>
@@ -145,6 +146,7 @@ struct dw_spi {
unsigned long dma_chan_busy;
dma_addr_t dma_addr; /* phy address of the Data register */
const struct dw_spi_dma_ops *dma_ops;
+ struct completion dma_completion;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs;
--
2.26.2
^ permalink raw reply related
* [PATCH v6 15/16] spi: dw: Use regset32 DebugFS method to create regdump file
From: Serge Semin @ 2020-05-29 13:12 UTC (permalink / raw)
To: Mark Brown
Cc: Serge Semin, Serge Semin, Andy Shevchenko, Georgy Vlasov,
Ramil Zaripov, Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann,
Feng Tang, Rob Herring, linux-mips, devicetree, linux-spi,
linux-kernel
In-Reply-To: <20200529131205.31838-1-Sergey.Semin@baikalelectronics.ru>
DebugFS kernel interface provides a dedicated method to create the
registers dump file. Use it instead of creating a generic DebugFS
file with manually written read callback function.
Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Georgy Vlasov <Georgy.Vlasov@baikalelectronics.ru>
Cc: Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru>
Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Feng Tang <feng.tang@intel.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: linux-mips@vger.kernel.org
Cc: devicetree@vger.kernel.org
---
Changelog v3:
- Add commas in the debugfs_reg32 structure initializer and after the last
item of the array dw_spi_dbgfs_regs.
---
drivers/spi/spi-dw-core.c | 86 ++++++++++++---------------------------
drivers/spi/spi-dw.h | 2 +
2 files changed, 28 insertions(+), 60 deletions(-)
diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index 4d1849699a12..323c66c5db50 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -29,66 +29,29 @@ struct chip_data {
};
#ifdef CONFIG_DEBUG_FS
-#define SPI_REGS_BUFSIZE 1024
-static ssize_t dw_spi_show_regs(struct file *file, char __user *user_buf,
- size_t count, loff_t *ppos)
-{
- struct dw_spi *dws = file->private_data;
- char *buf;
- u32 len = 0;
- ssize_t ret;
-
- buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL);
- if (!buf)
- return 0;
-
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "%s registers:\n", dev_name(&dws->master->dev));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "=================================\n");
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "CTRLR0: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR0));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "CTRLR1: \t0x%08x\n", dw_readl(dws, DW_SPI_CTRLR1));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "SSIENR: \t0x%08x\n", dw_readl(dws, DW_SPI_SSIENR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "SER: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SER));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "BAUDR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_BAUDR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "TXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_TXFTLR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "RXFTLR: \t0x%08x\n", dw_readl(dws, DW_SPI_RXFTLR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "TXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_TXFLR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "RXFLR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_RXFLR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "SR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_SR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "IMR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_IMR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "ISR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_ISR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "DMACR: \t\t0x%08x\n", dw_readl(dws, DW_SPI_DMACR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "DMATDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMATDLR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "DMARDLR: \t0x%08x\n", dw_readl(dws, DW_SPI_DMARDLR));
- len += scnprintf(buf + len, SPI_REGS_BUFSIZE - len,
- "=================================\n");
-
- ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
- kfree(buf);
- return ret;
+
+#define DW_SPI_DBGFS_REG(_name, _off) \
+{ \
+ .name = _name, \
+ .offset = _off, \
}
-static const struct file_operations dw_spi_regs_ops = {
- .owner = THIS_MODULE,
- .open = simple_open,
- .read = dw_spi_show_regs,
- .llseek = default_llseek,
+static const struct debugfs_reg32 dw_spi_dbgfs_regs[] = {
+ DW_SPI_DBGFS_REG("CTRLR0", DW_SPI_CTRLR0),
+ DW_SPI_DBGFS_REG("CTRLR1", DW_SPI_CTRLR1),
+ DW_SPI_DBGFS_REG("SSIENR", DW_SPI_SSIENR),
+ DW_SPI_DBGFS_REG("SER", DW_SPI_SER),
+ DW_SPI_DBGFS_REG("BAUDR", DW_SPI_BAUDR),
+ DW_SPI_DBGFS_REG("TXFTLR", DW_SPI_TXFTLR),
+ DW_SPI_DBGFS_REG("RXFTLR", DW_SPI_RXFTLR),
+ DW_SPI_DBGFS_REG("TXFLR", DW_SPI_TXFLR),
+ DW_SPI_DBGFS_REG("RXFLR", DW_SPI_RXFLR),
+ DW_SPI_DBGFS_REG("SR", DW_SPI_SR),
+ DW_SPI_DBGFS_REG("IMR", DW_SPI_IMR),
+ DW_SPI_DBGFS_REG("ISR", DW_SPI_ISR),
+ DW_SPI_DBGFS_REG("DMACR", DW_SPI_DMACR),
+ DW_SPI_DBGFS_REG("DMATDLR", DW_SPI_DMATDLR),
+ DW_SPI_DBGFS_REG("DMARDLR", DW_SPI_DMARDLR),
};
static int dw_spi_debugfs_init(struct dw_spi *dws)
@@ -100,8 +63,11 @@ static int dw_spi_debugfs_init(struct dw_spi *dws)
if (!dws->debugfs)
return -ENOMEM;
- debugfs_create_file("registers", S_IFREG | S_IRUGO,
- dws->debugfs, (void *)dws, &dw_spi_regs_ops);
+ dws->regset.regs = dw_spi_dbgfs_regs;
+ dws->regset.nregs = ARRAY_SIZE(dw_spi_dbgfs_regs);
+ dws->regset.base = dws->regs;
+ debugfs_create_regset32("registers", 0400, dws->debugfs, &dws->regset);
+
return 0;
}
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index 0b2cd7994513..151ba316619e 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -3,6 +3,7 @@
#define DW_SPI_HEADER_H
#include <linux/completion.h>
+#include <linux/debugfs.h>
#include <linux/irqreturn.h>
#include <linux/io.h>
#include <linux/scatterlist.h>
@@ -152,6 +153,7 @@ struct dw_spi {
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs;
+ struct debugfs_regset32 regset;
#endif
};
--
2.26.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox