Re: [RFC,v3 7/9] media: platform: Add Mediatek ISP P1 device driver

Tomasz Figa
2019-08-07T13:25:52Z

On Wed, Aug 7, 2019 at 11:11 AM Jungo Lin <jungo.lin@mediatek.com> wrote:
>
> Hi, Tomasz:
>
> On Tue, 2019-08-06 at 18:47 +0900, Tomasz Figa wrote:
> > Hi Jungo,
> >
> > On Fri, Jul 26, 2019 at 4:24 PM Jungo Lin <jungo.lin@mediatek.com> wrote:
> > >
> > > Hi, Tomasz:
> > >
> > > On Thu, 2019-07-25 at 18:23 +0900, Tomasz Figa wrote:
> > > > .Hi Jungo,
> > > >
> > > > On Sat, Jul 20, 2019 at 6:58 PM Jungo Lin <jungo.lin@mediatek.com> wrote:
> > > > >
> > > > > Hi, Tomasz:
> > > > >
> > > > > On Wed, 2019-07-10 at 18:56 +0900, Tomasz Figa wrote:
> > > > > > Hi Jungo,
> > > > > >
> > > > > > On Tue, Jun 11, 2019 at 11:53:42AM +0800, Jungo Lin wrote:
> > [snip]
>
> I just keep some questions to be clarified.
> [snip]
>
> > > > > > > + isp_dev->meta0_vb2_index = meta0_vb2_index;
> > > > > > > + isp_dev->meta1_vb2_index = meta1_vb2_index;
> > > > > > > + } else {
> > > > > > > + if (irq_status & SOF_INT_ST) {
> > > > > > > + isp_dev->current_frame = hw_frame_num;
> > > > > > > + isp_dev->meta0_vb2_index = meta0_vb2_index;
> > > > > > > + isp_dev->meta1_vb2_index = meta1_vb2_index;
> > > > > > > + }
> > > > > > > + irq_handle_notify_event(isp_dev, irq_status, dma_status, 1);
> > > > > > > + }
> > > > > >
> > > > > > The if and else blocks do almost the same things just in different order. Is
> > > > > > it really expected?
> > > > > >
> > > > >
> > > > > If we receive HW_PASS1_DON_ST & SOF_INT_ST IRQ events at the same time,
> > > > > the correct sequence should be handle HW_PASS1_DON_ST firstly to check
> > > > > any de-queued frame and update the next frame setting later.
> > > > > Normally, this is a corner case or system performance issue.
> > > >
> > > > So it sounds like HW_PASS1_DON_ST means that all data from current
> > > > frame has been written, right? If I understand your explanation above
> > > > correctly, that would mean following handling of each interrupt:
> > > >
> > > > HW_PASS1_DON_ST:
> > > > - CQ executes with next CQ buffer to prepare for next frame. <- how
> > > > is this handled? does the CQ hardware automatically receive this event
> > > > from the ISP hadware?
> > > > - return VB2 buffers,
> > > > - complete requests.
> > > >
> > > > SOF_INT_ST:
> > > > - send VSYNC event to userspace,
> > > > - program next CQ buffer to CQ,
> > > >
> > > > SW_PASS1_DON_ST:
> > > > - reclaim CQ buffer and enqueue next frame to composing if available
> > > >
> > >
> > > Sorry for our implementation of HW_PASS1_DON_ST.
> > > It is confusing.
> > > Below is the revised version based on your conclusion.
> > > So in our new implemmenation, we just handle SOF_INT_ST &
> > > SW_PASS1_DON_ST events. We just add one warning message for
> > > HW_PASS1_DON_ST
> > >
> > > HW_PASS1_DON_ST:
> > > - CQ executes with next CQ buffer to prepare for next frame.
> > >
> > > SOF_INT_ST:
> > > - send VSYNC event to userspace,
> > > - program next CQ buffer to CQ,
> > >
> > > SW_PASS1_DON_ST:
> > > - reclaim CQ buffer and enqueue next frame to composing if available
> > > - return VB2 buffers,
> > > - complete requests.
> > >
> > > For CQ HW operations, it is listed below:
> > >
> > > a. The CQ buffer has two kinds of information
> > > - Which ISP registers needs to be updated.
> > > - Where the corresponding ISP register data to be read.
> > > b. The CQ buffer loading procedure is triggered by HW_PASS1_DONT_ST IRQ
> > > event periodically.
> > > - Normally, if the ISP HW receives the completed frame and it will
> > > trigger W_PASS1_DONT_ST IRQ and perform CQ buffer loading immediately.
> > > - So the CQ buffer loading is performed by ISP HW automatically.
> > > c. The ISP HW will read CQ base address register(REG_CQ_THR0_BASEADDR)
> > > to decide which CQ buffer is loaded.
> > > - So we configure the next CQ base address in SOF.
> > > d. For CQ buffer loading, CQ will read the ISP registers from CQ buffer
> > > and update the ISP register values into HW.
> > > - SCP composer will compose one dummy CQ buffer and assign it to
> > > REG_CQ_THR0_BASEADDR of each CQ buffer.
> > > - Dummy CQ buffer has no updated ISP registers comparing with other
> > > CQ buffers.
> > > - With this design, if there is no updated new CQ buffer by driver
> > > which may be caused no en-queue frames from user space. The CQ HW will
> > > load dummy CQ buffer and do nothing.
> >
> > Does the set of registers programmed by CQ include destination buffer
> > addresses to? If yes, we would end up overwriting previous frames if
> > no new buffers are provided.
> >
>
> Yes, the buffer addresses are changed per frame request. We need to
> compose CQ to include these DMA destination addresses. For your concern,
> we have DMA flow buffer control (FBC) in HW. If there is no FBC counter
> increased due to no buffer for each DMA, the ISP HW doesn't output the
> data to the corresponding DMA address.
>
> Below is the simple descriptor of CQ buffer.
> a. ISP registers in tuning buffer, including 3A registers.
> b. All capture buffers informations.
> - DMA buffer destination address
> - FBC counter
> c. Some specif ISP registers for meta DMAs, such as LCE or LMVO.
> d. frame sequence number register
>

Okay, with the FBC counter it sounds fine. Thanks for clarifying.

> > > f. The CQ buffer loading is guaranteed by HW to finish before the next
> > > SOF.
> > >
> >
> > Okay, thanks a lot for the explanation. This is much more clear now.
> >
> > [snip]
> > > > > > > +static const struct dev_pm_ops mtk_isp_pm_ops = {
> > > > > > > + SET_SYSTEM_SLEEP_PM_OPS(mtk_isp_suspend, mtk_isp_resume)
> > > > > > > + SET_RUNTIME_PM_OPS(mtk_isp_suspend, mtk_isp_resume, NULL)
> > > > > >
> > > > > > For V4L2 drivers system and runtime PM ops would normally be completely
> > > > > > different. Runtime PM ops would be called when the hardware is idle already
> > > > > > or is about to become active. System PM ops would be called at system power
> > > > > > state change and the hardware might be both idle or active. Please also see
> > > > > > my comments to mtk_isp_suspend() and mtk_isp_resume() above.
> > > > > >
> > > > >
> > > > > Here is the new implementation. It should be clear to show the
> > > > > difference between system and runtime PM ops.
> > > > >
> > > > > static const struct dev_pm_ops mtk_isp_pm_ops = {
> > > > > SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > > > > pm_runtime_force_resume)
> > > > > SET_RUNTIME_PM_OPS(mtk_isp_runtime_suspend, mtk_isp_runtime_resume,
> > > > > NULL)
> > > > > };
> > > >
> > > > That's still not correct. In runtime suspend/resume ops we already are
> > > > not streaming anymore, because we call pm_runtime_get/put_*() when
> > > > starting and stopping streaming. In system suspend/resume ops we might
> > > > be streaming and that's when we need to stop the hardware and wait for
> > > > it to finish. Please implement these ops separately.
> > > >
> > > > Best regards,
> > > > Tomasz
> > >
> > >
> > > Ok, got your point.
> > > Below is the new implementation for your review.
> > >
> > > static int mtk_isp_pm_suspend(struct device *dev)
> > > {
> > > struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> > > u32 val;
> > > int ret;
> > >
> > > dev_dbg(dev, "- %s\n", __func__);
> > >
> > > /* Check ISP is streaming or not */
> > > if (!p1_dev->cam_dev.streaming)
> > > goto done;
> >
> > We would normally check here for pm_runtime_suspended(). Although they
> > both should be equivalent. Still, there is no need to call
> > pm_runtime_force_suspend() if the latter is true, so we could just
> > return 0 instantly.
> >
>
> Ok, here is the fixed version.
>
> static int mtk_isp_pm_suspend(struct device *dev)
> {
> struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> u32 val;
> int ret;
>
> dev_dbg(dev, "- %s\n", __func__);
>
> if (pm_runtime_suspended(dev))
> return 0;
>
> /* Disable ISP's view finder and wait for TG idle */
> dev_dbg(dev, "cam suspend, disable VF\n");
> val = readl(p1_dev->regs + REG_TG_VF_CON);
> writel(val & (~TG_VF_CON_VFDATA_EN), p1_dev->regs + REG_TG_VF_CON);
> ret = readl_poll_timeout_atomic(p1_dev->regs + REG_TG_INTER_ST, val,
> (val & TG_CS_MASK) == TG_IDLE_ST,
> USEC_PER_MSEC, MTK_ISP_STOP_HW_TIMEOUT);
> if (ret)
> dev_warn(dev, "can't stop HW:%d:0x%x\n", ret, val);

What happens in this case? Is it safe to continue?

>
> /* Disable CMOS */
> val = readl(p1_dev->regs + REG_TG_SEN_MODE);
> writel(val & (~TG_SEN_MODE_CMOS_EN), p1_dev->regs + REG_TG_SEN_MODE);
>
> /* Force ISP HW to idle */
> ret = pm_runtime_force_suspend(dev);
> if (ret)
> return ret;

We should probably reenable the hardware if the above failed, so that
we hopefully end up in the same state as before the suspend.

>
> return 0;
> }
> [snip]
>
> > > static int mtk_isp_pm_resume(struct device *dev)
> > > {
> > > struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> > > u32 val;
> > > int ret;
> > >
> > > dev_dbg(dev, "- %s\n", __func__);
> > >
> > > /* Force ISP HW to resume if needed */
> > > ret = pm_runtime_force_resume(dev);
> > > if (ret)
> > > return ret;
> >
> > We should do this conditionally based on what pm_runtime_suspended()
> > returns. If it's non-zero then we can just return 0 instantly.
> >
>
> Ok, here is the fixed version.
>
> static int mtk_isp_pm_resume(struct device *dev)
> {
> struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> u32 val;
> int ret;
>
> dev_dbg(dev, "- %s\n", __func__);
>
> if (pm_runtime_suspended(dev))
> return 0;
>
> /* Force ISP HW to resume */
> ret = pm_runtime_force_resume(dev);
> if (ret)
> return ret;
>
> /* Enable CMOS */
> dev_dbg(dev, "cam resume, enable CMOS/VF\n");
> val = readl(p1_dev->regs + REG_TG_SEN_MODE);
> writel(val | TG_SEN_MODE_CMOS_EN, p1_dev->regs + REG_TG_SEN_MODE);
>
> /* Enable VF */
> val = readl(p1_dev->regs + REG_TG_VF_CON);
> writel(val | TG_VF_CON_VFDATA_EN, p1_dev->regs + REG_TG_VF_CON);
>
> return 0;
> }
>
> [snip]
>
> > > static int mtk_isp_runtime_suspend(struct device *dev)
> > > {
> > > struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> > >
> > > dev_dbg(dev, "- %s\n", __func__);
> > >
> > > if (pm_runtime_suspended(dev))
> > > return 0;
> >
> > Sorry, I guess I wasn't clear in my reply. It's not possible to get
> > this callback called if the device is already runtime suspended.
> >
>
> Ok, got it. Need to remove pm_runtime_suspended(dev) checking and move
> it into mtk_isp_pm_* functions. If I still don't get your point, could
> you kindly provide one sample driver for reference?

The above implementation is okay, thanks. :)

> Based on current
> implementation, it is similar to below drivers.
> https://elixir.bootlin.com/linux/latest/source/drivers/media/platform/mtk-mdp/mtk_mdp_core.c#L255
> https://elixir.bootlin.com/linux/latest/source/drivers/media/platform/exynos4-is/fimc-is-i2c.c#L113
>

The first one is an m2m device so it has slightly different rules -
the runtime PM is allowed to suspend between frames if the idle time
is long enough. The second one is a dummy driver for some fake i2c
bus, so it doesn't really have any meaningful implementation.

I think you could take a look at
https://elixir.bootlin.com/linux/v5.3-rc3/source/drivers/media/platform/exynos4-is/fimc-lite.c#L1550
, which is an online capture device too.

>
> static int mtk_isp_runtime_suspend(struct device *dev)
> {
> struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
>
> dev_dbg(dev, "%s:disable clock\n", __func__);
> clk_bulk_disable_unprepare(p1_dev->num_clks, p1_dev->clks);
>
> return 0;
> }
>
> [snip]
>
> > > static int mtk_isp_runtime_resume(struct device *dev)
> > > {
> > > struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> > > int ret;
> > >
> > > dev_dbg(dev, "- %s\n", __func__);
> > >
> > > if (pm_runtime_suspended(dev))
> > > return 0;
> >
> > In this case the above call would always return non-zero, so the
> > behavior wouldn't be very good.
> >
>
> Same as above.
>
> static int mtk_isp_runtime_resume(struct device *dev)
> {
> struct mtk_isp_p1_device *p1_dev = dev_get_drvdata(dev);
> int ret;
>
> dev_dbg(dev, "%s:enable clock\n", __func__);
> ret = clk_bulk_prepare_enable(p1_dev->num_clks, p1_dev->clks);
> if (ret) {
> dev_err(dev, "failed to enable clock:%d\n", ret);
> return ret;
> }
>
> return 0;
> }

Makes sense, thanks!

Best regards,
Tomasz