Message ID | 20220905230406.30801-1-prabhakar.mahadev-lad.rj@bp.renesas.com |
---|---|
Headers | show |
Series | Add driver for CSI2 and CRU modules found on Renesas RZ/G2L SoC | expand |
Hi Prabhakar, Thank you for the patch. On Tue, Sep 06, 2022 at 12:04:05AM +0100, Lad Prabhakar wrote: > Add MIPI CSI-2 receiver driver for Renesas RZ/G2L. The MIPI > CSI-2 is part of the CRU module found on RZ/G2L family. > > Based on a patch in the BSP by Hien Huynh > <hien.huynh.px@renesas.com> > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > --- > v1 -> v2 > * Sorted Kconfig select > * Prefixed generic names for struct/variables with rzg2_csi2 > * Dropped unnecessary checks for remote source > * Dropped exporting functions > * Moved lane validation to probe > * Split up rzg2l_csi2_dphy_setting() and rzg2l_csi2_mipi_link_setting() > * Used rzg2l_csi2_write() wherever possible > * Dropped stream_count/lock > * Used active subdev state instead of manually storing format in driver > * Implemented init_cfg/enum_frame_size/enum_mbus_code callbacks > * Dropped check for bus_type of remote source > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > the mipi/dhpy initialization happens as per the HW manual That doesn't look right. The driver doesn't use runtime PM anymore, so power domains may not be handled properly. What was the problem with clock handling using runtime PM ? > * Hardcoded VC0 usage for now as streams API is under development > > RFC v2 -> v1 > * Fixed initialization sequence of DPHY and link > * Exported DPHY and link initialization functions so that the > CRU core driver can initialize the CRU and CSI2 as per the HW manual. > > RFC v1 -> RFC v2 > * new patch (split up as new driver compared to v1) > --- > drivers/media/platform/renesas/Kconfig | 1 + > drivers/media/platform/renesas/Makefile | 1 + > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 761 ++++++++++++++++++ > .../platform/renesas/rzg2l-cru/rzg2l-csi2.h | 46 ++ > 6 files changed, 829 insertions(+) > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Kconfig > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Makefile > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig > index 9fd90672ea2d..ed788e991f74 100644 > --- a/drivers/media/platform/renesas/Kconfig > +++ b/drivers/media/platform/renesas/Kconfig > @@ -41,6 +41,7 @@ config VIDEO_SH_VOU > Support for the Video Output Unit (VOU) on SuperH SoCs. > > source "drivers/media/platform/renesas/rcar-vin/Kconfig" > +source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" > > # Mem2mem drivers > > diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile > index 3ec226ef5fd2..55854e868887 100644 > --- a/drivers/media/platform/renesas/Makefile > +++ b/drivers/media/platform/renesas/Makefile > @@ -4,6 +4,7 @@ > # > > obj-y += rcar-vin/ > +obj-y += rzg2l-cru/ > obj-y += vsp1/ > > obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > new file mode 100644 > index 000000000000..57c40bb499df > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > @@ -0,0 +1,17 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +config VIDEO_RZG2L_CSI2 > + tristate "RZ/G2L MIPI CSI-2 Receiver" > + depends on ARCH_RENESAS || COMPILE_TEST > + depends on V4L_PLATFORM_DRIVERS > + depends on VIDEO_DEV && OF > + select MEDIA_CONTROLLER > + select RESET_CONTROLLER > + select V4L2_FWNODE > + select VIDEO_V4L2_SUBDEV_API > + help > + Support for Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 > + Receiver driver. > + > + To compile this driver as a module, choose M here: the > + module will be called rzg2l-csi2. > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > new file mode 100644 > index 000000000000..91ea97a944e6 > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > @@ -0,0 +1,3 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > new file mode 100644 > index 000000000000..1f6838ed64fc > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > @@ -0,0 +1,761 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Driver for Renesas RZ/G2L MIPI CSI-2 Receiver > + * > + * Copyright (C) 2022 Renesas Electronics Corp. > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/of_graph.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/reset.h> > +#include <linux/sys_soc.h> > +#include <linux/units.h> > + > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-mc.h> > +#include <media/v4l2-subdev.h> > + > +#include "rzg2l-csi2.h" > + > +/* LINK registers */ > +/* Module Configuration Register */ > +#define CSI2nMCG 0x0 > +#define CSI2nMCG_SDLN GENMASK(11, 8) > + > +/* Module Control Register 0 */ > +#define CSI2nMCT0 0x10 > +#define CSI2nMCT0_VDLN(x) ((x) << 0) > + > +/* Module Control Register 2 */ > +#define CSI2nMCT2 0x18 > +#define CSI2nMCT2_FRRSKW(x) ((x) << 16) > +#define CSI2nMCT2_FRRCLK(x) ((x) << 0) > + > +/* Module Control Register 3 */ > +#define CSI2nMCT3 0x1c > +#define CSI2nMCT3_RXEN BIT(0) > + > +/* Reset Control Register */ > +#define CSI2nRTCT 0x28 > +#define CSI2nRTCT_VSRST BIT(0) > + > +/* Reset Status Register */ > +#define CSI2nRTST 0x2c > +#define CSI2nRTST_VSRSTS BIT(0) > + > +/* Receive Data Type Enable Low Register */ > +#define CSI2nDTEL 0x60 > + > +/* Receive Data Type Enable High Register */ > +#define CSI2nDTEH 0x64 > + > +/* Power Management Status Register */ > +#define CSI2nPMST 0x200 > + > +/* Power Management Status Clear Register */ > +#define CSI2nPMSC 0x204 > + > +/* DPHY registers */ > +/* D-PHY Control Register 0 */ > +#define CSIDPHYCTRL0 0x400 > +#define CSIDPHYCTRL0_EN_LDO1200 BIT(1) > +#define CSIDPHYCTRL0_EN_BGR BIT(0) > + > +/* D-PHY Timing Register 0 */ > +#define CSIDPHYTIM0 0x404 > +#define CSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) > +#define CSIDPHYTIM0_T_INIT(x) ((x) << 0) > + > +/* D-PHY Timing Register 1 */ > +#define CSIDPHYTIM1 0x408 > +#define CSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) > +#define CSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) > +#define CSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) > +#define CSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) > + > +/* D-PHY Skew Adjustment Function */ > +#define CSIDPHYSKW0 0x460 > +#define CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(x) ((x) & 0x3) > +#define CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(x) (((x) & 0x3) << 4) > +#define CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(x) (((x) & 0x3) << 8) > +#define CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(x) (((x) & 0x3) << 12) > +#define CSIDPHYSKW0_DEFAULT_SKW CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(1) | \ > + CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(1) | \ > + CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(1) | \ > + CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(1) > + > +#define VSRSTS_RETRIES 20 > + > +#define RZG2L_CSI2_DEFAULT_WIDTH 800 > +#define RZG2L_CSI2_DEFAULT_HEIGHT 600 > +#define RZG2L_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_UYVY8_1X16 > + > +struct rzg2l_csi2_timings { > + u32 t_init; > + u32 tclk_miss; > + u32 tclk_settle; > + u32 ths_settle; > + u32 tclk_prepare; > + u32 ths_prepare; > +}; > + > +enum rzg2l_dphy_timings { > + TRANSMISSION_RATE_80_MBPS = 0, > + TRANSMISSION_RATE_125_MBPS, > + TRANSMISSION_RATE_250_MBPS, > + TRANSMISSION_RATE_360_MBPS, > + TRANSMISSION_RATE_360_MBPS_PLUS, > +}; > + > +static const struct rzg2l_csi2_timings rzg2l_csi2_global_timings[] = { > + [TRANSMISSION_RATE_80_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 23, > + .ths_settle = 31, > + .tclk_prepare = 10, > + .ths_prepare = 19, > + }, > + [TRANSMISSION_RATE_125_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 23, > + .ths_settle = 28, > + .tclk_prepare = 10, > + .ths_prepare = 19, > + }, > + [TRANSMISSION_RATE_250_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 23, > + .ths_settle = 22, > + .tclk_prepare = 10, > + .ths_prepare = 16, > + }, > + [TRANSMISSION_RATE_360_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 18, > + .ths_settle = 19, > + .tclk_prepare = 10, > + .ths_prepare = 10, > + }, > + [TRANSMISSION_RATE_360_MBPS_PLUS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 18, > + .ths_settle = 18, > + .tclk_prepare = 10, > + .ths_prepare = 10, > + }, > +}; > + > +struct rzg2l_csi2_format { > + u32 code; > + unsigned int datatype; > + unsigned int bpp; > +}; > + > +static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { > + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, > +}; > + > +static const struct rzg2l_csi2_format *rzg2l_csi2_code_to_fmt(unsigned int code) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_formats); i++) > + if (rzg2l_csi2_formats[i].code == code) > + return &rzg2l_csi2_formats[i]; > + > + return NULL; > +} > + > +static inline struct rzg2l_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) > +{ > + return container_of(n, struct rzg2l_csi2, notifier); > +} > + > +static u32 rzg2l_csi2_read(struct rzg2l_csi2 *csi2, unsigned int reg) > +{ > + return ioread32(csi2->base + reg); > +} > + > +static void rzg2l_csi2_write(struct rzg2l_csi2 *csi2, unsigned int reg, > + u32 data) > +{ > + iowrite32(data, csi2->base + reg); > +} > + > +static void rzg2l_csi2_set(struct rzg2l_csi2 *csi2, unsigned int reg, u32 set) > +{ > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) | set); > +} > + > +static void rzg2l_csi2_clr(struct rzg2l_csi2 *csi2, unsigned int reg, u32 clr) > +{ > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) & ~clr); > +} > + > +static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) > +{ > + struct v4l2_subdev *sd = csi2->remote_source; Could you rename sd to source ? A local sd variable usually refers to the subdev associated with the driver, it would be a bit confusing here. > + const struct rzg2l_csi2_format *format; > + const struct v4l2_mbus_framefmt *fmt; > + struct v4l2_subdev_state *state; > + struct v4l2_ctrl *ctrl; > + u64 mbps; > + > + /* Read the pixel rate control from remote. */ > + ctrl = v4l2_ctrl_find(sd->ctrl_handler, V4L2_CID_PIXEL_RATE); > + if (!ctrl) { > + dev_err(csi2->dev, "no pixel rate control in subdev %s\n", > + sd->name); > + return -EINVAL; > + } > + > + state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); > + fmt = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); This isn't right, the first argument is the remote subdev, and the state is for the local subdev. I think you meant fmt = v4l2_subdev_get_pad_format(&csi2->subdev, state, RZG2L_CSI2_SINK); > + v4l2_subdev_unlock_state(state); > + format = rzg2l_csi2_code_to_fmt(fmt->code); > + > + /* > + * Calculate hsfreq in Mbps > + * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes > + */ > + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; > + do_div(mbps, csi2->lanes * 1000000); > + > + return mbps; > +} > + > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2) > +{ > + return reset_control_deassert(csi2->rstc); > +} This function is called by the CRU driver just before call .s_stream(1). Let's drop it, and move the reset_control_deassert() call to rzg2l_csi2_s_stream(). > + > +/* ----------------------------------------------------------------------------- > + * DPHY setting > + */ > + > +static int rzg2l_csi2_dphy_disable(struct rzg2l_csi2 *csi2) > +{ > + int ret; > + > + /* Reset the CRU (D-PHY) */ > + ret = reset_control_assert(csi2->rstc); > + if (ret) > + return ret; > + > + /* Stop the D-PHY clock */ > + clk_disable_unprepare(csi2->sysclk); > + > + /* Cancel the EN_LDO1200 register setting */ > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > + > + /* Cancel the EN_BGR register setting */ > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > + > + return 0; > +} > + > +static int rzg2l_csi2_dphy_enable(struct rzg2l_csi2 *csi2) > +{ > + const struct rzg2l_csi2_timings *dphy_timing; > + u32 dphytim0, dphytim1; > + int mbps; > + int ret; > + > + mbps = rzg2l_csi2_calc_mbps(csi2); > + if (mbps < 0) > + return mbps; > + > + csi2->hsfreq = mbps; > + > + /* Set DPHY timing parameters */ > + if (csi2->hsfreq <= 80) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_80_MBPS]; > + else if (csi2->hsfreq <= 125) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_125_MBPS]; > + else if (csi2->hsfreq <= 250) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_250_MBPS]; > + else if (csi2->hsfreq <= 360) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS]; > + else if (csi2->hsfreq <= 1500) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS_PLUS]; > + else > + return -EINVAL; Add a max_hsfreq field to rzg2l_csi2_timings, and write for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_global_timings); ++i) { dphy_timing = &rzg2l_csi2_global_timings[i]; if (csi2->hsfreq <= dphy_timing->max_hsfreq) break; } You can then drop the rzg2l_dphy_timings enum. > + > + /* Set D-PHY timing parameters */ > + dphytim0 = CSIDPHYTIM0_TCLK_MISS(dphy_timing->tclk_miss) | > + CSIDPHYTIM0_T_INIT(dphy_timing->t_init); > + dphytim1 = CSIDPHYTIM1_THS_PREPARE(dphy_timing->ths_prepare) | > + CSIDPHYTIM1_TCLK_PREPARE(dphy_timing->tclk_prepare) | > + CSIDPHYTIM1_THS_SETTLE(dphy_timing->ths_settle) | > + CSIDPHYTIM1_TCLK_SETTLE(dphy_timing->tclk_settle); > + rzg2l_csi2_write(csi2, CSIDPHYTIM0, dphytim0); > + rzg2l_csi2_write(csi2, CSIDPHYTIM1, dphytim1); > + > + /* Enable D-PHY power control 0 */ > + rzg2l_csi2_write(csi2, CSIDPHYSKW0, CSIDPHYSKW0_DEFAULT_SKW); > + > + /* Set the EN_BGR bit */ > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > + > + /* Delay 20us to be stable */ > + usleep_range(20, 40); > + > + /* Enable D-PHY power control 1 */ > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > + > + /* Delay 10us to be stable */ > + usleep_range(10, 20); > + > + /* Start supplying the internal clock for the D-PHY block */ > + ret = clk_prepare_enable(csi2->sysclk); > + if (ret) > + rzg2l_csi2_dphy_disable(csi2); > + > + return ret; > +} > + > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on) > +{ > + if (on) > + return rzg2l_csi2_dphy_enable(csi2); > + > + return rzg2l_csi2_dphy_disable(csi2); > +} Does this work without exporting the symbol when the drivers are compiled as modules ? > + > +static void rzg2l_csi2_mipi_link_enable(struct rzg2l_csi2 *csi2) > +{ > + unsigned long vclk_rate = clk_get_rate(csi2->vclk) / HZ_PER_MHZ; > + u32 frrskw, frrclk, frrskw_coeff, frrclk_coeff; > + > + /* Select data lanes */ > + rzg2l_csi2_write(csi2, CSI2nMCT0, CSI2nMCT0_VDLN(csi2->lanes)); > + > + frrskw_coeff = 3 * vclk_rate * 8; > + frrclk_coeff = frrskw_coeff / 2; > + frrskw = DIV_ROUND_UP(frrskw_coeff, csi2->hsfreq); > + frrclk = DIV_ROUND_UP(frrclk_coeff, csi2->hsfreq); > + rzg2l_csi2_write(csi2, CSI2nMCT2, CSI2nMCT2_FRRSKW(frrskw) | > + CSI2nMCT2_FRRCLK(frrclk)); > + > + /* > + * Select data type. > + * FS, FE, LS, LE, Generic Short Packet Codes 1 to 8, > + * Generic Long Packet Data Types 1 to 4 YUV422 8-bit, > + * RGB565, RGB888, RAW8 to RAW20, User-defined 8-bit > + * data types 1 to 8 > + */ > + rzg2l_csi2_write(csi2, CSI2nDTEL, 0xf778ff0f); > + rzg2l_csi2_write(csi2, CSI2nDTEH, 0x00ffff1f); > + > + /* Enable LINK reception */ > + rzg2l_csi2_write(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > +} > + > +static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) > +{ > + unsigned int timeout = VSRSTS_RETRIES; > + > + /* Stop LINK reception */ > + rzg2l_csi2_clr(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > + > + /* Request a software reset of the LINK Video Pixel Interface */ > + rzg2l_csi2_write(csi2, CSI2nRTCT, CSI2nRTCT_VSRST); > + > + /* Make sure CSI2nRTST.VSRSTS bit is cleared */ > + while (timeout--) { > + if (!(rzg2l_csi2_read(csi2, CSI2nRTST) & CSI2nRTST_VSRSTS)) > + break; > + usleep_range(100, 200); > + }; > + > + if (!timeout) > + dev_err(csi2->dev, "Clearing CSI2nRTST.VSRSTS timed out\n"); > +} > + > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on) Can this also be moved to rzg2l_csi2_s_stream() instead of being called from the CRU driver ? I really dislike the manual calls between the two drivers. This will mess up future reworks of the subdev infrastructure :-( Please try to stick to the standard code flow as much as possible. > +{ > + if (on) > + rzg2l_csi2_mipi_link_enable(csi2); > + else > + rzg2l_csi2_mipi_link_disable(csi2); > +} > + > +static int rzg2l_csi2_s_stream(struct v4l2_subdev *sd, int enable) > +{ > + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); > + > + return v4l2_subdev_call(csi2->remote_source, video, s_stream, enable); > +} > + > +static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, > + struct v4l2_subdev_format *fmt) > +{ > + struct v4l2_mbus_framefmt *src_format; > + struct v4l2_mbus_framefmt *format; > + > + if (fmt->pad > RZG2L_CSI2_SINK) > + return -EINVAL; That's not right, set_format must not return an error on the source pad. Have you run v4l2-compliance on this driver ? Please include the v4l2-compliance report in the cover letter. As the CSI-2 receiver has the same format on the input and output, you can simply write src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE); if (fmt->pad == RZG2L_CSI2_SOURCE) { fmt->format = *src_format; return 0; } here. I'd also rename format to sink_format and write sink_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE); at the beginning of the function. > + > + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); > + > + if (!rzg2l_csi2_code_to_fmt(fmt->format.code)) > + format->code = rzg2l_csi2_formats[0].code; > + else > + format->code = fmt->format.code; > + > + format->field = V4L2_FIELD_NONE; > + format->colorspace = V4L2_COLORSPACE_SRGB; The CSI-2 receiver doesn't care about color spaces, so I would simply copy all colorspace-related fields received from userspace (colorspace, xfer_func, ycbcr_enc and quantization). > + format->width = fmt->format.width; > + format->height = fmt->format.height; > + fmt->format = *format; > + > + /* propagate format to source pad */ > + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE_VC0); > + *src_format = *format; > + > + return 0; > +} > + > +static int rzg2l_csi2_init_config(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state) > +{ > + struct v4l2_subdev_format fmt = { 0 }; > + > + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; When using the subdev active state, the state pointer passed to this function will never be null. That's fine, as rzg2l_csi2_set_format() doesn't use the which field. I'd just drop this line. Please set the pad to RZG2L_CSI2_SINK explicitly. It works as-is because RZG2L_CSI2_SINK is equal to zero, and the structure is zeroed above, but let's not depend on that. > + fmt.format.width = RZG2L_CSI2_DEFAULT_WIDTH; > + fmt.format.height = RZG2L_CSI2_DEFAULT_HEIGHT; > + fmt.format.field = V4L2_FIELD_NONE; > + fmt.format.code = RZG2L_CSI2_DEFAULT_FMT; > + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; > + > + return rzg2l_csi2_set_format(sd, sd_state, &fmt); > +} > + > +static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + if (code->index >= ARRAY_SIZE(rzg2l_csi2_formats)) > + return -EINVAL; > + > + code->code = rzg2l_csi2_formats[code->index].code; > + > + return 0; > +} > + > +static int rzg2l_csi2_enum_frame_size(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_frame_size_enum *fse) > +{ > + if (fse->index != 0) > + return -EINVAL; > + > + fse->min_width = clamp_t(u32, 1, 320, 2800); > + fse->min_height = clamp_t(u32, 1, 240, 4095); > + fse->max_width = clamp_t(u32, -1, 320, 2800); > + fse->max_height = clamp_t(u32, -1, 240, 4095); That's a weird way to write fse->min_width = 320; fse->min_height = 240; fse->max_width = 2800; fse->max_height = 4095; Could you add macros for those values, the same way you already have RZG2L_CSI2_DEFAULT_WIDTH and RZG2L_CSI2_DEFAULT_HEIGHT ? rzg2l_csi2_set_format() should also clamp the width and height accordingly. > + > + return 0; > +} > + > +static const struct v4l2_subdev_video_ops rzg2l_csi2_video_ops = { > + .s_stream = rzg2l_csi2_s_stream, > +}; > + > +static const struct v4l2_subdev_pad_ops rzg2l_csi2_pad_ops = { > + .enum_mbus_code = rzg2l_csi2_enum_mbus_code, > + .init_cfg = rzg2l_csi2_init_config, > + .enum_frame_size = rzg2l_csi2_enum_frame_size, > + .set_fmt = rzg2l_csi2_set_format, > + .get_fmt = v4l2_subdev_get_fmt, > +}; > + > +static const struct v4l2_subdev_ops rzg2l_csi2_subdev_ops = { > + .video = &rzg2l_csi2_video_ops, > + .pad = &rzg2l_csi2_pad_ops, > +}; > + > +/* ----------------------------------------------------------------------------- > + * Async handling and registration of subdevices and links. > + */ > + > +static int rzg2l_csi2_notify_bound(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > + > + csi2->remote_source = subdev; > + > + dev_dbg(csi2->dev, "Bound subdev: %s pad\n", subdev->name); > + > + return media_create_pad_link(&subdev->entity, RZG2L_CSI2_SINK, > + &csi2->subdev.entity, 0, > + MEDIA_LNK_FL_ENABLED | > + MEDIA_LNK_FL_IMMUTABLE); > +} > + > +static void rzg2l_csi2_notify_unbind(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > + > + csi2->remote_source = NULL; > + > + dev_dbg(csi2->dev, "Unbind subdev %s\n", subdev->name); > +} > + > +static const struct v4l2_async_notifier_operations rzg2l_csi2_notify_ops = { > + .bound = rzg2l_csi2_notify_bound, > + .unbind = rzg2l_csi2_notify_unbind, > +}; > + > +static int rzg2l_csi2_parse_v4l2(struct rzg2l_csi2 *csi2, > + struct v4l2_fwnode_endpoint *vep) > +{ > + /* Only port 0 endpoint 0 is valid. */ > + if (vep->base.port || vep->base.id) > + return -ENOTCONN; > + > + csi2->lanes = vep->bus.mipi_csi2.num_data_lanes; > + > + return 0; > +} > + > +static int rzg2l_csi2_parse_dt(struct rzg2l_csi2 *csi2) > +{ > + struct v4l2_fwnode_endpoint v4l2_ep = { > + .bus_type = V4L2_MBUS_CSI2_DPHY > + }; > + struct v4l2_async_subdev *asd; > + struct fwnode_handle *fwnode; > + struct fwnode_handle *ep; > + int ret; > + > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, 0); > + if (!ep) { > + dev_err(csi2->dev, "Not connected to subdevice\n"); > + return -EINVAL; > + } > + > + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); > + if (ret) { > + dev_err(csi2->dev, "Could not parse v4l2 endpoint\n"); > + fwnode_handle_put(ep); > + return -EINVAL; > + } > + > + ret = rzg2l_csi2_parse_v4l2(csi2, &v4l2_ep); > + if (ret) { > + fwnode_handle_put(ep); > + return ret; > + } > + > + fwnode = fwnode_graph_get_remote_endpoint(ep); > + fwnode_handle_put(ep); > + > + dev_dbg(csi2->dev, "Found '%pOF'\n", to_of_node(fwnode)); > + > + v4l2_async_nf_init(&csi2->notifier); > + csi2->notifier.ops = &rzg2l_csi2_notify_ops; > + > + asd = v4l2_async_nf_add_fwnode(&csi2->notifier, fwnode, > + struct v4l2_async_subdev); > + fwnode_handle_put(fwnode); > + if (IS_ERR(asd)) > + return PTR_ERR(asd); > + > + ret = v4l2_async_subdev_nf_register(&csi2->subdev, &csi2->notifier); > + if (ret) > + v4l2_async_nf_cleanup(&csi2->notifier); > + > + return ret; > +} > + > +static int rzg2l_validate_csi2_lanes(struct rzg2l_csi2 *csi2) > +{ > + int ret = 0; > + int lanes; > + > + if (csi2->lanes != 1 && csi2->lanes != 2 && csi2->lanes != 4) { > + dev_err(csi2->dev, "Unsupported number of data-lanes: %u\n", > + csi2->lanes); > + return -EINVAL; > + } > + > + ret = clk_prepare_enable(csi2->pclk); > + if (ret) > + return ret; > + > + /* Checking the maximum lanes support for CSI-2 module */ > + lanes = (rzg2l_csi2_read(csi2, CSI2nMCG) & CSI2nMCG_SDLN) >> 8; > + if (lanes < csi2->lanes) { > + dev_err(csi2->dev, > + "Failed to support %d data lanes\n", csi2->lanes); > + ret = -EINVAL; > + } > + > + clk_disable_unprepare(csi2->pclk); > + > + return ret; > +} > + > +/* ----------------------------------------------------------------------------- > + * Platform Device Driver. > + */ > + > +static const struct media_entity_operations rzg2l_csi2_entity_ops = { > + .link_validate = v4l2_subdev_link_validate, > +}; > + > +static int rzg2l_csi2_probe(struct platform_device *pdev) > +{ > + struct rzg2l_csi2 *csi2; > + int ret; > + > + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); > + if (!csi2) > + return -ENOMEM; > + > + csi2->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(csi2->base)) > + return PTR_ERR(csi2->base); > + > + csi2->rstc = devm_reset_control_get(&pdev->dev, "cmn-rstb"); > + if (IS_ERR(csi2->rstc)) > + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->rstc), > + "failed to get cpg cmn-rstb\n"); > + > + csi2->sysclk = devm_clk_get(&pdev->dev, "sysclk"); > + if (IS_ERR(csi2->sysclk)) { > + dev_err(&pdev->dev, "Failed to get sysclk"); > + return PTR_ERR(csi2->sysclk); > + } > + > + csi2->vclk = devm_clk_get(&pdev->dev, "vclk"); > + if (IS_ERR(csi2->vclk)) { > + dev_err(&pdev->dev, "Failed to get vclk"); > + return PTR_ERR(csi2->vclk); > + } > + > + csi2->pclk = devm_clk_get(&pdev->dev, "pclk"); > + if (IS_ERR(csi2->pclk)) { > + dev_err(&pdev->dev, "Failed to get pclk"); > + return PTR_ERR(csi2->pclk); > + } Could devm_clk_bulk_get() help ? > + > + csi2->dev = &pdev->dev; > + > + platform_set_drvdata(pdev, csi2); > + > + ret = rzg2l_csi2_parse_dt(csi2); > + if (ret) > + return ret; > + > + ret = rzg2l_validate_csi2_lanes(csi2); > + if (ret) > + return ret; > + > + csi2->subdev.dev = &pdev->dev; > + v4l2_subdev_init(&csi2->subdev, &rzg2l_csi2_subdev_ops); > + v4l2_set_subdevdata(&csi2->subdev, &pdev->dev); > + snprintf(csi2->subdev.name, sizeof(csi2->subdev.name), > + "csi-%s", dev_name(&pdev->dev)); > + csi2->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; > + > + csi2->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > + csi2->subdev.entity.ops = &rzg2l_csi2_entity_ops; > + > + csi2->pads[RZG2L_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; > + /* > + * TODO: RZ/G2L CSI2 supports 4 virtual channels, as virtual > + * channels should be implemented by streams API which is under > + * development lets hardcode to VC0 for now. > + */ > + csi2->pads[RZG2L_CSI2_SOURCE_VC0].flags = MEDIA_PAD_FL_SOURCE; > + ret = media_entity_pads_init(&csi2->subdev.entity, 2, csi2->pads); > + if (ret) > + goto error_async; > + > + pm_runtime_enable(&pdev->dev); > + > + ret = v4l2_subdev_init_finalize(&csi2->subdev); > + if (ret < 0) > + goto error_pm; > + > + /* enable pclk for register access */ > + ret = clk_prepare_enable(csi2->pclk); > + if (ret) > + goto error_pm; Keeping the clock always on isn't great from a power management point of view. Runtime PM would be much better. > + > + ret = v4l2_async_register_subdev(&csi2->subdev); > + if (ret < 0) > + goto error_subdev; > + > + dev_info(csi2->dev, "%d lanes found\n", csi2->lanes); > + > + return 0; > + > +error_subdev: > + clk_disable_unprepare(csi2->pclk); > + v4l2_subdev_cleanup(&csi2->subdev); > +error_pm: > + pm_runtime_disable(&pdev->dev); > +error_async: > + v4l2_async_nf_unregister(&csi2->notifier); > + v4l2_async_nf_cleanup(&csi2->notifier); > + > + return ret; > +} > + > +static const struct of_device_id rzg2l_csi2_of_table[] = { > + { .compatible = "renesas,rzg2l-csi2", }, > + { /* sentinel */ } > +}; > + > +static int rzg2l_csi2_remove(struct platform_device *pdev) > +{ > + struct rzg2l_csi2 *csi2 = platform_get_drvdata(pdev); > + > + v4l2_async_nf_unregister(&csi2->notifier); > + v4l2_async_nf_cleanup(&csi2->notifier); > + v4l2_async_unregister_subdev(&csi2->subdev); > + clk_disable_unprepare(csi2->pclk); > + v4l2_subdev_cleanup(&csi2->subdev); > + > + pm_runtime_disable(&pdev->dev); > + > + return 0; > +} > + > +static struct platform_driver rzg2l_csi2_pdrv = { > + .remove = rzg2l_csi2_remove, > + .probe = rzg2l_csi2_probe, > + .driver = { > + .name = "rzg2l-csi2", > + .of_match_table = rzg2l_csi2_of_table, > + }, > +}; > + > +module_platform_driver(rzg2l_csi2_pdrv); > + > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > +MODULE_DESCRIPTION("Renesas RZ/G2L MIPI CSI2 receiver driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > new file mode 100644 > index 000000000000..f85a1d44250f > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > @@ -0,0 +1,46 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright (C) 2022 Renesas Electronics Corp. > + */ > + > +#ifndef __RZG2L_CSI2__ > +#define __RZG2L_CSI2__ > + > +enum rzg2l_csi2_pads { > + RZG2L_CSI2_SINK = 0, > + RZG2L_CSI2_SOURCE_VC0, > + RZG2L_CSI2_SOURCE_VC1, > + RZG2L_CSI2_SOURCE_VC2, > + RZG2L_CSI2_SOURCE_VC3, Drop RZG2L_CSI2_SOURCE_VC[123] and rename RZG2L_CSI2_SOURCE_VC0 to RZG2L_CSI2_SOURCE. > + NR_OF_RZG2L_CSI2_PAD, > +}; > + > +struct rzg2l_csi2 { > + struct device *dev; > + void __iomem *base; > + struct reset_control *rstc; > + struct clk *sysclk; > + struct clk *vclk; > + struct clk *pclk; > + > + struct v4l2_subdev subdev; > + struct media_pad pads[NR_OF_RZG2L_CSI2_PAD]; > + > + struct v4l2_async_notifier notifier; > + struct v4l2_subdev *remote_source; > + > + unsigned short lanes; > + > + unsigned long hsfreq; > +}; > + > +static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) > +{ > + return container_of(sd, struct rzg2l_csi2, subdev); > +} > + > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2); > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on); > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on); Let's make the API as opaque as possible for the CRU driver. I'd move the definition of the rzg2l_csi2 structure to the .c file, along with the sd_to_csi2() function, and change these three functions to take a v4l2_subdev pointer. > + > +#endif
Hi Prabhakar, Thanks for the set. On Tue, Sep 06, 2022 at 12:04:05AM +0100, Lad Prabhakar wrote: > Add MIPI CSI-2 receiver driver for Renesas RZ/G2L. The MIPI > CSI-2 is part of the CRU module found on RZ/G2L family. > > Based on a patch in the BSP by Hien Huynh > <hien.huynh.px@renesas.com> > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > --- > v1 -> v2 > * Sorted Kconfig select > * Prefixed generic names for struct/variables with rzg2_csi2 > * Dropped unnecessary checks for remote source > * Dropped exporting functions > * Moved lane validation to probe > * Split up rzg2l_csi2_dphy_setting() and rzg2l_csi2_mipi_link_setting() > * Used rzg2l_csi2_write() wherever possible > * Dropped stream_count/lock > * Used active subdev state instead of manually storing format in driver > * Implemented init_cfg/enum_frame_size/enum_mbus_code callbacks > * Dropped check for bus_type of remote source > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > the mipi/dhpy initialization happens as per the HW manual > * Hardcoded VC0 usage for now as streams API is under development > > RFC v2 -> v1 > * Fixed initialization sequence of DPHY and link > * Exported DPHY and link initialization functions so that the > CRU core driver can initialize the CRU and CSI2 as per the HW manual. > > RFC v1 -> RFC v2 > * new patch (split up as new driver compared to v1) > --- > drivers/media/platform/renesas/Kconfig | 1 + > drivers/media/platform/renesas/Makefile | 1 + > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 761 ++++++++++++++++++ > .../platform/renesas/rzg2l-cru/rzg2l-csi2.h | 46 ++ > 6 files changed, 829 insertions(+) > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Kconfig > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Makefile > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig > index 9fd90672ea2d..ed788e991f74 100644 > --- a/drivers/media/platform/renesas/Kconfig > +++ b/drivers/media/platform/renesas/Kconfig > @@ -41,6 +41,7 @@ config VIDEO_SH_VOU > Support for the Video Output Unit (VOU) on SuperH SoCs. > > source "drivers/media/platform/renesas/rcar-vin/Kconfig" > +source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" > > # Mem2mem drivers > > diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile > index 3ec226ef5fd2..55854e868887 100644 > --- a/drivers/media/platform/renesas/Makefile > +++ b/drivers/media/platform/renesas/Makefile > @@ -4,6 +4,7 @@ > # > > obj-y += rcar-vin/ > +obj-y += rzg2l-cru/ > obj-y += vsp1/ > > obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > new file mode 100644 > index 000000000000..57c40bb499df > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > @@ -0,0 +1,17 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +config VIDEO_RZG2L_CSI2 > + tristate "RZ/G2L MIPI CSI-2 Receiver" > + depends on ARCH_RENESAS || COMPILE_TEST > + depends on V4L_PLATFORM_DRIVERS > + depends on VIDEO_DEV && OF > + select MEDIA_CONTROLLER > + select RESET_CONTROLLER > + select V4L2_FWNODE > + select VIDEO_V4L2_SUBDEV_API > + help > + Support for Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 > + Receiver driver. > + > + To compile this driver as a module, choose M here: the > + module will be called rzg2l-csi2. > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > new file mode 100644 > index 000000000000..91ea97a944e6 > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > @@ -0,0 +1,3 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > new file mode 100644 > index 000000000000..1f6838ed64fc > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > @@ -0,0 +1,761 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Driver for Renesas RZ/G2L MIPI CSI-2 Receiver > + * > + * Copyright (C) 2022 Renesas Electronics Corp. > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/of_graph.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/reset.h> > +#include <linux/sys_soc.h> > +#include <linux/units.h> > + > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-mc.h> > +#include <media/v4l2-subdev.h> > + > +#include "rzg2l-csi2.h" > + > +/* LINK registers */ > +/* Module Configuration Register */ > +#define CSI2nMCG 0x0 > +#define CSI2nMCG_SDLN GENMASK(11, 8) > + > +/* Module Control Register 0 */ > +#define CSI2nMCT0 0x10 > +#define CSI2nMCT0_VDLN(x) ((x) << 0) > + > +/* Module Control Register 2 */ > +#define CSI2nMCT2 0x18 > +#define CSI2nMCT2_FRRSKW(x) ((x) << 16) > +#define CSI2nMCT2_FRRCLK(x) ((x) << 0) > + > +/* Module Control Register 3 */ > +#define CSI2nMCT3 0x1c > +#define CSI2nMCT3_RXEN BIT(0) > + > +/* Reset Control Register */ > +#define CSI2nRTCT 0x28 > +#define CSI2nRTCT_VSRST BIT(0) > + > +/* Reset Status Register */ > +#define CSI2nRTST 0x2c > +#define CSI2nRTST_VSRSTS BIT(0) > + > +/* Receive Data Type Enable Low Register */ > +#define CSI2nDTEL 0x60 > + > +/* Receive Data Type Enable High Register */ > +#define CSI2nDTEH 0x64 > + > +/* Power Management Status Register */ > +#define CSI2nPMST 0x200 > + > +/* Power Management Status Clear Register */ > +#define CSI2nPMSC 0x204 > + > +/* DPHY registers */ > +/* D-PHY Control Register 0 */ > +#define CSIDPHYCTRL0 0x400 > +#define CSIDPHYCTRL0_EN_LDO1200 BIT(1) > +#define CSIDPHYCTRL0_EN_BGR BIT(0) > + > +/* D-PHY Timing Register 0 */ > +#define CSIDPHYTIM0 0x404 > +#define CSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) > +#define CSIDPHYTIM0_T_INIT(x) ((x) << 0) > + > +/* D-PHY Timing Register 1 */ > +#define CSIDPHYTIM1 0x408 > +#define CSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) > +#define CSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) > +#define CSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) > +#define CSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) > + > +/* D-PHY Skew Adjustment Function */ > +#define CSIDPHYSKW0 0x460 > +#define CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(x) ((x) & 0x3) > +#define CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(x) (((x) & 0x3) << 4) > +#define CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(x) (((x) & 0x3) << 8) > +#define CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(x) (((x) & 0x3) << 12) > +#define CSIDPHYSKW0_DEFAULT_SKW CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(1) | \ > + CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(1) | \ > + CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(1) | \ > + CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(1) > + > +#define VSRSTS_RETRIES 20 > + > +#define RZG2L_CSI2_DEFAULT_WIDTH 800 > +#define RZG2L_CSI2_DEFAULT_HEIGHT 600 > +#define RZG2L_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_UYVY8_1X16 > + > +struct rzg2l_csi2_timings { > + u32 t_init; > + u32 tclk_miss; > + u32 tclk_settle; > + u32 ths_settle; > + u32 tclk_prepare; > + u32 ths_prepare; > +}; > + > +enum rzg2l_dphy_timings { > + TRANSMISSION_RATE_80_MBPS = 0, > + TRANSMISSION_RATE_125_MBPS, > + TRANSMISSION_RATE_250_MBPS, > + TRANSMISSION_RATE_360_MBPS, > + TRANSMISSION_RATE_360_MBPS_PLUS, > +}; > + > +static const struct rzg2l_csi2_timings rzg2l_csi2_global_timings[] = { > + [TRANSMISSION_RATE_80_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 23, > + .ths_settle = 31, > + .tclk_prepare = 10, > + .ths_prepare = 19, > + }, > + [TRANSMISSION_RATE_125_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 23, > + .ths_settle = 28, > + .tclk_prepare = 10, > + .ths_prepare = 19, > + }, > + [TRANSMISSION_RATE_250_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 23, > + .ths_settle = 22, > + .tclk_prepare = 10, > + .ths_prepare = 16, > + }, > + [TRANSMISSION_RATE_360_MBPS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 18, > + .ths_settle = 19, > + .tclk_prepare = 10, > + .ths_prepare = 10, > + }, > + [TRANSMISSION_RATE_360_MBPS_PLUS] = { > + .t_init = 79801, > + .tclk_miss = 4, > + .tclk_settle = 18, > + .ths_settle = 18, > + .tclk_prepare = 10, > + .ths_prepare = 10, > + }, > +}; > + > +struct rzg2l_csi2_format { > + u32 code; > + unsigned int datatype; > + unsigned int bpp; > +}; > + > +static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { > + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, > +}; > + > +static const struct rzg2l_csi2_format *rzg2l_csi2_code_to_fmt(unsigned int code) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_formats); i++) > + if (rzg2l_csi2_formats[i].code == code) > + return &rzg2l_csi2_formats[i]; > + > + return NULL; > +} > + > +static inline struct rzg2l_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) > +{ > + return container_of(n, struct rzg2l_csi2, notifier); > +} > + > +static u32 rzg2l_csi2_read(struct rzg2l_csi2 *csi2, unsigned int reg) > +{ > + return ioread32(csi2->base + reg); > +} > + > +static void rzg2l_csi2_write(struct rzg2l_csi2 *csi2, unsigned int reg, > + u32 data) > +{ > + iowrite32(data, csi2->base + reg); > +} > + > +static void rzg2l_csi2_set(struct rzg2l_csi2 *csi2, unsigned int reg, u32 set) > +{ > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) | set); > +} > + > +static void rzg2l_csi2_clr(struct rzg2l_csi2 *csi2, unsigned int reg, u32 clr) > +{ > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) & ~clr); > +} > + > +static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) > +{ > + struct v4l2_subdev *sd = csi2->remote_source; > + const struct rzg2l_csi2_format *format; > + const struct v4l2_mbus_framefmt *fmt; > + struct v4l2_subdev_state *state; > + struct v4l2_ctrl *ctrl; > + u64 mbps; > + > + /* Read the pixel rate control from remote. */ > + ctrl = v4l2_ctrl_find(sd->ctrl_handler, V4L2_CID_PIXEL_RATE); > + if (!ctrl) { > + dev_err(csi2->dev, "no pixel rate control in subdev %s\n", > + sd->name); > + return -EINVAL; > + } > + > + state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); > + fmt = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); > + v4l2_subdev_unlock_state(state); > + format = rzg2l_csi2_code_to_fmt(fmt->code); You shouldn't access fmt once you've unlocked subdev state. > + > + /* > + * Calculate hsfreq in Mbps > + * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes > + */ > + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; > + do_div(mbps, csi2->lanes * 1000000); > + > + return mbps; > +} > + > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2) > +{ > + return reset_control_deassert(csi2->rstc); > +} > + > +/* ----------------------------------------------------------------------------- > + * DPHY setting > + */ > + > +static int rzg2l_csi2_dphy_disable(struct rzg2l_csi2 *csi2) > +{ > + int ret; > + > + /* Reset the CRU (D-PHY) */ > + ret = reset_control_assert(csi2->rstc); > + if (ret) > + return ret; > + > + /* Stop the D-PHY clock */ > + clk_disable_unprepare(csi2->sysclk); > + > + /* Cancel the EN_LDO1200 register setting */ > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > + > + /* Cancel the EN_BGR register setting */ > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > + > + return 0; > +} > + > +static int rzg2l_csi2_dphy_enable(struct rzg2l_csi2 *csi2) > +{ > + const struct rzg2l_csi2_timings *dphy_timing; > + u32 dphytim0, dphytim1; > + int mbps; > + int ret; > + > + mbps = rzg2l_csi2_calc_mbps(csi2); > + if (mbps < 0) > + return mbps; > + > + csi2->hsfreq = mbps; > + > + /* Set DPHY timing parameters */ > + if (csi2->hsfreq <= 80) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_80_MBPS]; > + else if (csi2->hsfreq <= 125) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_125_MBPS]; > + else if (csi2->hsfreq <= 250) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_250_MBPS]; > + else if (csi2->hsfreq <= 360) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS]; > + else if (csi2->hsfreq <= 1500) > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS_PLUS]; > + else > + return -EINVAL; > + > + /* Set D-PHY timing parameters */ > + dphytim0 = CSIDPHYTIM0_TCLK_MISS(dphy_timing->tclk_miss) | > + CSIDPHYTIM0_T_INIT(dphy_timing->t_init); > + dphytim1 = CSIDPHYTIM1_THS_PREPARE(dphy_timing->ths_prepare) | > + CSIDPHYTIM1_TCLK_PREPARE(dphy_timing->tclk_prepare) | > + CSIDPHYTIM1_THS_SETTLE(dphy_timing->ths_settle) | > + CSIDPHYTIM1_TCLK_SETTLE(dphy_timing->tclk_settle); > + rzg2l_csi2_write(csi2, CSIDPHYTIM0, dphytim0); > + rzg2l_csi2_write(csi2, CSIDPHYTIM1, dphytim1); > + > + /* Enable D-PHY power control 0 */ > + rzg2l_csi2_write(csi2, CSIDPHYSKW0, CSIDPHYSKW0_DEFAULT_SKW); > + > + /* Set the EN_BGR bit */ > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > + > + /* Delay 20us to be stable */ > + usleep_range(20, 40); > + > + /* Enable D-PHY power control 1 */ > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > + > + /* Delay 10us to be stable */ > + usleep_range(10, 20); > + > + /* Start supplying the internal clock for the D-PHY block */ > + ret = clk_prepare_enable(csi2->sysclk); > + if (ret) > + rzg2l_csi2_dphy_disable(csi2); > + > + return ret; > +} > + > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on) > +{ > + if (on) > + return rzg2l_csi2_dphy_enable(csi2); > + > + return rzg2l_csi2_dphy_disable(csi2); > +} > + > +static void rzg2l_csi2_mipi_link_enable(struct rzg2l_csi2 *csi2) > +{ > + unsigned long vclk_rate = clk_get_rate(csi2->vclk) / HZ_PER_MHZ; > + u32 frrskw, frrclk, frrskw_coeff, frrclk_coeff; > + > + /* Select data lanes */ > + rzg2l_csi2_write(csi2, CSI2nMCT0, CSI2nMCT0_VDLN(csi2->lanes)); > + > + frrskw_coeff = 3 * vclk_rate * 8; > + frrclk_coeff = frrskw_coeff / 2; > + frrskw = DIV_ROUND_UP(frrskw_coeff, csi2->hsfreq); > + frrclk = DIV_ROUND_UP(frrclk_coeff, csi2->hsfreq); > + rzg2l_csi2_write(csi2, CSI2nMCT2, CSI2nMCT2_FRRSKW(frrskw) | > + CSI2nMCT2_FRRCLK(frrclk)); > + > + /* > + * Select data type. > + * FS, FE, LS, LE, Generic Short Packet Codes 1 to 8, > + * Generic Long Packet Data Types 1 to 4 YUV422 8-bit, > + * RGB565, RGB888, RAW8 to RAW20, User-defined 8-bit > + * data types 1 to 8 > + */ > + rzg2l_csi2_write(csi2, CSI2nDTEL, 0xf778ff0f); > + rzg2l_csi2_write(csi2, CSI2nDTEH, 0x00ffff1f); > + > + /* Enable LINK reception */ > + rzg2l_csi2_write(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > +} > + > +static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) > +{ > + unsigned int timeout = VSRSTS_RETRIES; > + > + /* Stop LINK reception */ > + rzg2l_csi2_clr(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > + > + /* Request a software reset of the LINK Video Pixel Interface */ > + rzg2l_csi2_write(csi2, CSI2nRTCT, CSI2nRTCT_VSRST); > + > + /* Make sure CSI2nRTST.VSRSTS bit is cleared */ > + while (timeout--) { > + if (!(rzg2l_csi2_read(csi2, CSI2nRTST) & CSI2nRTST_VSRSTS)) > + break; > + usleep_range(100, 200); > + }; > + > + if (!timeout) > + dev_err(csi2->dev, "Clearing CSI2nRTST.VSRSTS timed out\n"); > +} > + > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on) > +{ > + if (on) > + rzg2l_csi2_mipi_link_enable(csi2); > + else > + rzg2l_csi2_mipi_link_disable(csi2); > +} > + > +static int rzg2l_csi2_s_stream(struct v4l2_subdev *sd, int enable) > +{ > + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); > + > + return v4l2_subdev_call(csi2->remote_source, video, s_stream, enable); > +} > + > +static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, > + struct v4l2_subdev_format *fmt) > +{ > + struct v4l2_mbus_framefmt *src_format; > + struct v4l2_mbus_framefmt *format; > + > + if (fmt->pad > RZG2L_CSI2_SINK) > + return -EINVAL; > + > + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); > + > + if (!rzg2l_csi2_code_to_fmt(fmt->format.code)) > + format->code = rzg2l_csi2_formats[0].code; > + else > + format->code = fmt->format.code; > + > + format->field = V4L2_FIELD_NONE; > + format->colorspace = V4L2_COLORSPACE_SRGB; > + format->width = fmt->format.width; > + format->height = fmt->format.height; > + fmt->format = *format; > + > + /* propagate format to source pad */ > + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE_VC0); > + *src_format = *format; > + > + return 0; > +} > + > +static int rzg2l_csi2_init_config(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state) > +{ > + struct v4l2_subdev_format fmt = { 0 }; You could do this in variable initialisation. > + > + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; > + fmt.format.width = RZG2L_CSI2_DEFAULT_WIDTH; > + fmt.format.height = RZG2L_CSI2_DEFAULT_HEIGHT; > + fmt.format.field = V4L2_FIELD_NONE; > + fmt.format.code = RZG2L_CSI2_DEFAULT_FMT; > + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; > + > + return rzg2l_csi2_set_format(sd, sd_state, &fmt); > +} > + > +static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + if (code->index >= ARRAY_SIZE(rzg2l_csi2_formats)) > + return -EINVAL; > + > + code->code = rzg2l_csi2_formats[code->index].code; > + > + return 0; > +} > + > +static int rzg2l_csi2_enum_frame_size(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_frame_size_enum *fse) > +{ > + if (fse->index != 0) > + return -EINVAL; > + > + fse->min_width = clamp_t(u32, 1, 320, 2800); > + fse->min_height = clamp_t(u32, 1, 240, 4095); > + fse->max_width = clamp_t(u32, -1, 320, 2800); > + fse->max_height = clamp_t(u32, -1, 240, 4095); > + > + return 0; > +} > + > +static const struct v4l2_subdev_video_ops rzg2l_csi2_video_ops = { > + .s_stream = rzg2l_csi2_s_stream, > +}; > + > +static const struct v4l2_subdev_pad_ops rzg2l_csi2_pad_ops = { > + .enum_mbus_code = rzg2l_csi2_enum_mbus_code, > + .init_cfg = rzg2l_csi2_init_config, > + .enum_frame_size = rzg2l_csi2_enum_frame_size, > + .set_fmt = rzg2l_csi2_set_format, > + .get_fmt = v4l2_subdev_get_fmt, > +}; > + > +static const struct v4l2_subdev_ops rzg2l_csi2_subdev_ops = { > + .video = &rzg2l_csi2_video_ops, > + .pad = &rzg2l_csi2_pad_ops, > +}; > + > +/* ----------------------------------------------------------------------------- > + * Async handling and registration of subdevices and links. > + */ > + > +static int rzg2l_csi2_notify_bound(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > + > + csi2->remote_source = subdev; > + > + dev_dbg(csi2->dev, "Bound subdev: %s pad\n", subdev->name); > + > + return media_create_pad_link(&subdev->entity, RZG2L_CSI2_SINK, > + &csi2->subdev.entity, 0, > + MEDIA_LNK_FL_ENABLED | > + MEDIA_LNK_FL_IMMUTABLE); > +} > + > +static void rzg2l_csi2_notify_unbind(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > + > + csi2->remote_source = NULL; > + > + dev_dbg(csi2->dev, "Unbind subdev %s\n", subdev->name); > +} > + > +static const struct v4l2_async_notifier_operations rzg2l_csi2_notify_ops = { > + .bound = rzg2l_csi2_notify_bound, > + .unbind = rzg2l_csi2_notify_unbind, > +}; > + > +static int rzg2l_csi2_parse_v4l2(struct rzg2l_csi2 *csi2, > + struct v4l2_fwnode_endpoint *vep) > +{ > + /* Only port 0 endpoint 0 is valid. */ > + if (vep->base.port || vep->base.id) > + return -ENOTCONN; > + > + csi2->lanes = vep->bus.mipi_csi2.num_data_lanes; > + > + return 0; > +} > + > +static int rzg2l_csi2_parse_dt(struct rzg2l_csi2 *csi2) > +{ > + struct v4l2_fwnode_endpoint v4l2_ep = { > + .bus_type = V4L2_MBUS_CSI2_DPHY > + }; > + struct v4l2_async_subdev *asd; > + struct fwnode_handle *fwnode; > + struct fwnode_handle *ep; > + int ret; > + > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, 0); > + if (!ep) { > + dev_err(csi2->dev, "Not connected to subdevice\n"); > + return -EINVAL; > + } > + > + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); > + if (ret) { > + dev_err(csi2->dev, "Could not parse v4l2 endpoint\n"); > + fwnode_handle_put(ep); > + return -EINVAL; > + } > + > + ret = rzg2l_csi2_parse_v4l2(csi2, &v4l2_ep); > + if (ret) { > + fwnode_handle_put(ep); > + return ret; > + } > + > + fwnode = fwnode_graph_get_remote_endpoint(ep); > + fwnode_handle_put(ep); > + > + dev_dbg(csi2->dev, "Found '%pOF'\n", to_of_node(fwnode)); You can enable debug message printing in v4l2 fwnode, and sysfs now has some, too. I'd suggest dropping this line . > + > + v4l2_async_nf_init(&csi2->notifier); > + csi2->notifier.ops = &rzg2l_csi2_notify_ops; > + > + asd = v4l2_async_nf_add_fwnode(&csi2->notifier, fwnode, > + struct v4l2_async_subdev); > + fwnode_handle_put(fwnode); > + if (IS_ERR(asd)) > + return PTR_ERR(asd); > + > + ret = v4l2_async_subdev_nf_register(&csi2->subdev, &csi2->notifier); > + if (ret) > + v4l2_async_nf_cleanup(&csi2->notifier); > + > + return ret; > +} > + > +static int rzg2l_validate_csi2_lanes(struct rzg2l_csi2 *csi2) > +{ > + int ret = 0; > + int lanes; > + > + if (csi2->lanes != 1 && csi2->lanes != 2 && csi2->lanes != 4) { > + dev_err(csi2->dev, "Unsupported number of data-lanes: %u\n", > + csi2->lanes); > + return -EINVAL; > + } > + > + ret = clk_prepare_enable(csi2->pclk); > + if (ret) > + return ret; > + > + /* Checking the maximum lanes support for CSI-2 module */ > + lanes = (rzg2l_csi2_read(csi2, CSI2nMCG) & CSI2nMCG_SDLN) >> 8; > + if (lanes < csi2->lanes) { > + dev_err(csi2->dev, > + "Failed to support %d data lanes\n", csi2->lanes); > + ret = -EINVAL; > + } > + > + clk_disable_unprepare(csi2->pclk); > + > + return ret; > +} > + > +/* ----------------------------------------------------------------------------- > + * Platform Device Driver. > + */ > + > +static const struct media_entity_operations rzg2l_csi2_entity_ops = { > + .link_validate = v4l2_subdev_link_validate, > +}; > + > +static int rzg2l_csi2_probe(struct platform_device *pdev) > +{ > + struct rzg2l_csi2 *csi2; > + int ret; > + > + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); > + if (!csi2) > + return -ENOMEM; > + > + csi2->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(csi2->base)) > + return PTR_ERR(csi2->base); > + > + csi2->rstc = devm_reset_control_get(&pdev->dev, "cmn-rstb"); > + if (IS_ERR(csi2->rstc)) > + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->rstc), > + "failed to get cpg cmn-rstb\n"); > + > + csi2->sysclk = devm_clk_get(&pdev->dev, "sysclk"); > + if (IS_ERR(csi2->sysclk)) { > + dev_err(&pdev->dev, "Failed to get sysclk"); > + return PTR_ERR(csi2->sysclk); > + } > + > + csi2->vclk = devm_clk_get(&pdev->dev, "vclk"); > + if (IS_ERR(csi2->vclk)) { > + dev_err(&pdev->dev, "Failed to get vclk"); > + return PTR_ERR(csi2->vclk); > + } > + > + csi2->pclk = devm_clk_get(&pdev->dev, "pclk"); > + if (IS_ERR(csi2->pclk)) { > + dev_err(&pdev->dev, "Failed to get pclk"); > + return PTR_ERR(csi2->pclk); > + } > + > + csi2->dev = &pdev->dev; > + > + platform_set_drvdata(pdev, csi2); > + > + ret = rzg2l_csi2_parse_dt(csi2); > + if (ret) > + return ret; > + > + ret = rzg2l_validate_csi2_lanes(csi2); > + if (ret) > + return ret; > + > + csi2->subdev.dev = &pdev->dev; > + v4l2_subdev_init(&csi2->subdev, &rzg2l_csi2_subdev_ops); > + v4l2_set_subdevdata(&csi2->subdev, &pdev->dev); > + snprintf(csi2->subdev.name, sizeof(csi2->subdev.name), > + "csi-%s", dev_name(&pdev->dev)); > + csi2->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; > + > + csi2->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > + csi2->subdev.entity.ops = &rzg2l_csi2_entity_ops; > + > + csi2->pads[RZG2L_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; > + /* > + * TODO: RZ/G2L CSI2 supports 4 virtual channels, as virtual > + * channels should be implemented by streams API which is under > + * development lets hardcode to VC0 for now. > + */ > + csi2->pads[RZG2L_CSI2_SOURCE_VC0].flags = MEDIA_PAD_FL_SOURCE; > + ret = media_entity_pads_init(&csi2->subdev.entity, 2, csi2->pads); > + if (ret) > + goto error_async; > + > + pm_runtime_enable(&pdev->dev); > + > + ret = v4l2_subdev_init_finalize(&csi2->subdev); > + if (ret < 0) > + goto error_pm; > + > + /* enable pclk for register access */ > + ret = clk_prepare_enable(csi2->pclk); > + if (ret) > + goto error_pm; > + > + ret = v4l2_async_register_subdev(&csi2->subdev); > + if (ret < 0) > + goto error_subdev; > + > + dev_info(csi2->dev, "%d lanes found\n", csi2->lanes); This is also printed if you enable debug level prints in v4l2 fwnode. > + > + return 0; > + > +error_subdev: > + clk_disable_unprepare(csi2->pclk); > + v4l2_subdev_cleanup(&csi2->subdev); > +error_pm: > + pm_runtime_disable(&pdev->dev); > +error_async: > + v4l2_async_nf_unregister(&csi2->notifier); > + v4l2_async_nf_cleanup(&csi2->notifier); > + > + return ret; > +} > + > +static const struct of_device_id rzg2l_csi2_of_table[] = { > + { .compatible = "renesas,rzg2l-csi2", }, > + { /* sentinel */ } > +}; > + > +static int rzg2l_csi2_remove(struct platform_device *pdev) > +{ > + struct rzg2l_csi2 *csi2 = platform_get_drvdata(pdev); > + > + v4l2_async_nf_unregister(&csi2->notifier); > + v4l2_async_nf_cleanup(&csi2->notifier); > + v4l2_async_unregister_subdev(&csi2->subdev); > + clk_disable_unprepare(csi2->pclk); > + v4l2_subdev_cleanup(&csi2->subdev); > + > + pm_runtime_disable(&pdev->dev); > + > + return 0; > +} > + > +static struct platform_driver rzg2l_csi2_pdrv = { > + .remove = rzg2l_csi2_remove, > + .probe = rzg2l_csi2_probe, > + .driver = { > + .name = "rzg2l-csi2", > + .of_match_table = rzg2l_csi2_of_table, > + }, > +}; > + > +module_platform_driver(rzg2l_csi2_pdrv); > + > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > +MODULE_DESCRIPTION("Renesas RZ/G2L MIPI CSI2 receiver driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > new file mode 100644 > index 000000000000..f85a1d44250f > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > @@ -0,0 +1,46 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright (C) 2022 Renesas Electronics Corp. > + */ > + > +#ifndef __RZG2L_CSI2__ > +#define __RZG2L_CSI2__ > + > +enum rzg2l_csi2_pads { > + RZG2L_CSI2_SINK = 0, > + RZG2L_CSI2_SOURCE_VC0, > + RZG2L_CSI2_SOURCE_VC1, > + RZG2L_CSI2_SOURCE_VC2, > + RZG2L_CSI2_SOURCE_VC3, > + NR_OF_RZG2L_CSI2_PAD, > +}; > + > +struct rzg2l_csi2 { > + struct device *dev; > + void __iomem *base; > + struct reset_control *rstc; > + struct clk *sysclk; > + struct clk *vclk; > + struct clk *pclk; > + > + struct v4l2_subdev subdev; > + struct media_pad pads[NR_OF_RZG2L_CSI2_PAD]; > + > + struct v4l2_async_notifier notifier; > + struct v4l2_subdev *remote_source; > + > + unsigned short lanes; > + > + unsigned long hsfreq; > +}; > + > +static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) > +{ > + return container_of(sd, struct rzg2l_csi2, subdev); > +} > + > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2); > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on); > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on); Are these something that could be achieved using the standard interfaces, as I believe the other drivers are doing? The pre_streamon and post_streamon callbacks could be relevant for this. > + > +#endif
Hi Laurent, Thank you for the review. On Wed, Sep 21, 2022 at 5:13 PM Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > Hi Prabhakar, > > Thank you for the patch. > > On Tue, Sep 06, 2022 at 12:04:05AM +0100, Lad Prabhakar wrote: > > Add MIPI CSI-2 receiver driver for Renesas RZ/G2L. The MIPI > > CSI-2 is part of the CRU module found on RZ/G2L family. > > > > Based on a patch in the BSP by Hien Huynh > > <hien.huynh.px@renesas.com> > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > --- > > v1 -> v2 > > * Sorted Kconfig select > > * Prefixed generic names for struct/variables with rzg2_csi2 > > * Dropped unnecessary checks for remote source > > * Dropped exporting functions > > * Moved lane validation to probe > > * Split up rzg2l_csi2_dphy_setting() and rzg2l_csi2_mipi_link_setting() > > * Used rzg2l_csi2_write() wherever possible > > * Dropped stream_count/lock > > * Used active subdev state instead of manually storing format in driver > > * Implemented init_cfg/enum_frame_size/enum_mbus_code callbacks > > * Dropped check for bus_type of remote source > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > the mipi/dhpy initialization happens as per the HW manual > > That doesn't look right. The driver doesn't use runtime PM anymore, so > power domains may not be handled properly. What was the problem with > clock handling using runtime PM ? > If we use the runtime PM all the clocks will be turned ON when we call pm_runtime_resume_and_get() which I dont want to. As per the "Starting reception for MIPI CSI-2 Input" section 35.3.1 for example we first need to turn ON all the clocks and later further down the line we need to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such cases I have switched to individual clock handling. > > * Hardcoded VC0 usage for now as streams API is under development > > > > RFC v2 -> v1 > > * Fixed initialization sequence of DPHY and link > > * Exported DPHY and link initialization functions so that the > > CRU core driver can initialize the CRU and CSI2 as per the HW manual. > > > > RFC v1 -> RFC v2 > > * new patch (split up as new driver compared to v1) > > --- > > drivers/media/platform/renesas/Kconfig | 1 + > > drivers/media/platform/renesas/Makefile | 1 + > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 761 ++++++++++++++++++ > > .../platform/renesas/rzg2l-cru/rzg2l-csi2.h | 46 ++ > > 6 files changed, 829 insertions(+) > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Kconfig > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Makefile > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > > > diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig > > index 9fd90672ea2d..ed788e991f74 100644 > > --- a/drivers/media/platform/renesas/Kconfig > > +++ b/drivers/media/platform/renesas/Kconfig > > @@ -41,6 +41,7 @@ config VIDEO_SH_VOU > > Support for the Video Output Unit (VOU) on SuperH SoCs. > > > > source "drivers/media/platform/renesas/rcar-vin/Kconfig" > > +source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" > > > > # Mem2mem drivers > > > > diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile > > index 3ec226ef5fd2..55854e868887 100644 > > --- a/drivers/media/platform/renesas/Makefile > > +++ b/drivers/media/platform/renesas/Makefile > > @@ -4,6 +4,7 @@ > > # > > > > obj-y += rcar-vin/ > > +obj-y += rzg2l-cru/ > > obj-y += vsp1/ > > > > obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > new file mode 100644 > > index 000000000000..57c40bb499df > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > @@ -0,0 +1,17 @@ > > +# SPDX-License-Identifier: GPL-2.0 > > + > > +config VIDEO_RZG2L_CSI2 > > + tristate "RZ/G2L MIPI CSI-2 Receiver" > > + depends on ARCH_RENESAS || COMPILE_TEST > > + depends on V4L_PLATFORM_DRIVERS > > + depends on VIDEO_DEV && OF > > + select MEDIA_CONTROLLER > > + select RESET_CONTROLLER > > + select V4L2_FWNODE > > + select VIDEO_V4L2_SUBDEV_API > > + help > > + Support for Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 > > + Receiver driver. > > + > > + To compile this driver as a module, choose M here: the > > + module will be called rzg2l-csi2. > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > new file mode 100644 > > index 000000000000..91ea97a944e6 > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > @@ -0,0 +1,3 @@ > > +# SPDX-License-Identifier: GPL-2.0 > > + > > +obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > > new file mode 100644 > > index 000000000000..1f6838ed64fc > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > > @@ -0,0 +1,761 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Driver for Renesas RZ/G2L MIPI CSI-2 Receiver > > + * > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + */ > > + > > +#include <linux/clk.h> > > +#include <linux/delay.h> > > +#include <linux/interrupt.h> > > +#include <linux/io.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/of_graph.h> > > +#include <linux/platform_device.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/reset.h> > > +#include <linux/sys_soc.h> > > +#include <linux/units.h> > > + > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-device.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mc.h> > > +#include <media/v4l2-subdev.h> > > + > > +#include "rzg2l-csi2.h" > > + > > +/* LINK registers */ > > +/* Module Configuration Register */ > > +#define CSI2nMCG 0x0 > > +#define CSI2nMCG_SDLN GENMASK(11, 8) > > + > > +/* Module Control Register 0 */ > > +#define CSI2nMCT0 0x10 > > +#define CSI2nMCT0_VDLN(x) ((x) << 0) > > + > > +/* Module Control Register 2 */ > > +#define CSI2nMCT2 0x18 > > +#define CSI2nMCT2_FRRSKW(x) ((x) << 16) > > +#define CSI2nMCT2_FRRCLK(x) ((x) << 0) > > + > > +/* Module Control Register 3 */ > > +#define CSI2nMCT3 0x1c > > +#define CSI2nMCT3_RXEN BIT(0) > > + > > +/* Reset Control Register */ > > +#define CSI2nRTCT 0x28 > > +#define CSI2nRTCT_VSRST BIT(0) > > + > > +/* Reset Status Register */ > > +#define CSI2nRTST 0x2c > > +#define CSI2nRTST_VSRSTS BIT(0) > > + > > +/* Receive Data Type Enable Low Register */ > > +#define CSI2nDTEL 0x60 > > + > > +/* Receive Data Type Enable High Register */ > > +#define CSI2nDTEH 0x64 > > + > > +/* Power Management Status Register */ > > +#define CSI2nPMST 0x200 > > + > > +/* Power Management Status Clear Register */ > > +#define CSI2nPMSC 0x204 > > + > > +/* DPHY registers */ > > +/* D-PHY Control Register 0 */ > > +#define CSIDPHYCTRL0 0x400 > > +#define CSIDPHYCTRL0_EN_LDO1200 BIT(1) > > +#define CSIDPHYCTRL0_EN_BGR BIT(0) > > + > > +/* D-PHY Timing Register 0 */ > > +#define CSIDPHYTIM0 0x404 > > +#define CSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) > > +#define CSIDPHYTIM0_T_INIT(x) ((x) << 0) > > + > > +/* D-PHY Timing Register 1 */ > > +#define CSIDPHYTIM1 0x408 > > +#define CSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) > > +#define CSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) > > +#define CSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) > > +#define CSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) > > + > > +/* D-PHY Skew Adjustment Function */ > > +#define CSIDPHYSKW0 0x460 > > +#define CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(x) ((x) & 0x3) > > +#define CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(x) (((x) & 0x3) << 4) > > +#define CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(x) (((x) & 0x3) << 8) > > +#define CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(x) (((x) & 0x3) << 12) > > +#define CSIDPHYSKW0_DEFAULT_SKW CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(1) | \ > > + CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(1) | \ > > + CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(1) | \ > > + CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(1) > > + > > +#define VSRSTS_RETRIES 20 > > + > > +#define RZG2L_CSI2_DEFAULT_WIDTH 800 > > +#define RZG2L_CSI2_DEFAULT_HEIGHT 600 > > +#define RZG2L_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_UYVY8_1X16 > > + > > +struct rzg2l_csi2_timings { > > + u32 t_init; > > + u32 tclk_miss; > > + u32 tclk_settle; > > + u32 ths_settle; > > + u32 tclk_prepare; > > + u32 ths_prepare; > > +}; > > + > > +enum rzg2l_dphy_timings { > > + TRANSMISSION_RATE_80_MBPS = 0, > > + TRANSMISSION_RATE_125_MBPS, > > + TRANSMISSION_RATE_250_MBPS, > > + TRANSMISSION_RATE_360_MBPS, > > + TRANSMISSION_RATE_360_MBPS_PLUS, > > +}; > > + > > +static const struct rzg2l_csi2_timings rzg2l_csi2_global_timings[] = { > > + [TRANSMISSION_RATE_80_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 23, > > + .ths_settle = 31, > > + .tclk_prepare = 10, > > + .ths_prepare = 19, > > + }, > > + [TRANSMISSION_RATE_125_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 23, > > + .ths_settle = 28, > > + .tclk_prepare = 10, > > + .ths_prepare = 19, > > + }, > > + [TRANSMISSION_RATE_250_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 23, > > + .ths_settle = 22, > > + .tclk_prepare = 10, > > + .ths_prepare = 16, > > + }, > > + [TRANSMISSION_RATE_360_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 18, > > + .ths_settle = 19, > > + .tclk_prepare = 10, > > + .ths_prepare = 10, > > + }, > > + [TRANSMISSION_RATE_360_MBPS_PLUS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 18, > > + .ths_settle = 18, > > + .tclk_prepare = 10, > > + .ths_prepare = 10, > > + }, > > +}; > > + > > +struct rzg2l_csi2_format { > > + u32 code; > > + unsigned int datatype; > > + unsigned int bpp; > > +}; > > + > > +static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { > > + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, > > +}; > > + > > +static const struct rzg2l_csi2_format *rzg2l_csi2_code_to_fmt(unsigned int code) > > +{ > > + unsigned int i; > > + > > + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_formats); i++) > > + if (rzg2l_csi2_formats[i].code == code) > > + return &rzg2l_csi2_formats[i]; > > + > > + return NULL; > > +} > > + > > +static inline struct rzg2l_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) > > +{ > > + return container_of(n, struct rzg2l_csi2, notifier); > > +} > > + > > +static u32 rzg2l_csi2_read(struct rzg2l_csi2 *csi2, unsigned int reg) > > +{ > > + return ioread32(csi2->base + reg); > > +} > > + > > +static void rzg2l_csi2_write(struct rzg2l_csi2 *csi2, unsigned int reg, > > + u32 data) > > +{ > > + iowrite32(data, csi2->base + reg); > > +} > > + > > +static void rzg2l_csi2_set(struct rzg2l_csi2 *csi2, unsigned int reg, u32 set) > > +{ > > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) | set); > > +} > > + > > +static void rzg2l_csi2_clr(struct rzg2l_csi2 *csi2, unsigned int reg, u32 clr) > > +{ > > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) & ~clr); > > +} > > + > > +static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) > > +{ > > + struct v4l2_subdev *sd = csi2->remote_source; > > Could you rename sd to source ? A local sd variable usually refers to > the subdev associated with the driver, it would be a bit confusing here. > Agreed, will do. > > + const struct rzg2l_csi2_format *format; > > + const struct v4l2_mbus_framefmt *fmt; > > + struct v4l2_subdev_state *state; > > + struct v4l2_ctrl *ctrl; > > + u64 mbps; > > + > > + /* Read the pixel rate control from remote. */ > > + ctrl = v4l2_ctrl_find(sd->ctrl_handler, V4L2_CID_PIXEL_RATE); > > + if (!ctrl) { > > + dev_err(csi2->dev, "no pixel rate control in subdev %s\n", > > + sd->name); > > + return -EINVAL; > > + } > > + > > + state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); > > + fmt = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); > > This isn't right, the first argument is the remote subdev, and the state > is for the local subdev. I think you meant > > fmt = v4l2_subdev_get_pad_format(&csi2->subdev, state, RZG2L_CSI2_SINK); > Ouch, yes you are right. > > + v4l2_subdev_unlock_state(state); > > + format = rzg2l_csi2_code_to_fmt(fmt->code); > > + > > + /* > > + * Calculate hsfreq in Mbps > > + * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes > > + */ > > + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; > > + do_div(mbps, csi2->lanes * 1000000); > > + > > + return mbps; > > +} > > + > > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2) > > +{ > > + return reset_control_deassert(csi2->rstc); > > +} > > This function is called by the CRU driver just before call .s_stream(1). > Let's drop it, and move the reset_control_deassert() call to > rzg2l_csi2_s_stream(). > Agreed, I have dropped this completely now. > > + > > +/* ----------------------------------------------------------------------------- > > + * DPHY setting > > + */ > > + > > +static int rzg2l_csi2_dphy_disable(struct rzg2l_csi2 *csi2) > > +{ > > + int ret; > > + > > + /* Reset the CRU (D-PHY) */ > > + ret = reset_control_assert(csi2->rstc); > > + if (ret) > > + return ret; > > + > > + /* Stop the D-PHY clock */ > > + clk_disable_unprepare(csi2->sysclk); > > + > > + /* Cancel the EN_LDO1200 register setting */ > > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > > + > > + /* Cancel the EN_BGR register setting */ > > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_dphy_enable(struct rzg2l_csi2 *csi2) > > +{ > > + const struct rzg2l_csi2_timings *dphy_timing; > > + u32 dphytim0, dphytim1; > > + int mbps; > > + int ret; > > + > > + mbps = rzg2l_csi2_calc_mbps(csi2); > > + if (mbps < 0) > > + return mbps; > > + > > + csi2->hsfreq = mbps; > > + > > + /* Set DPHY timing parameters */ > > + if (csi2->hsfreq <= 80) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_80_MBPS]; > > + else if (csi2->hsfreq <= 125) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_125_MBPS]; > > + else if (csi2->hsfreq <= 250) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_250_MBPS]; > > + else if (csi2->hsfreq <= 360) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS]; > > + else if (csi2->hsfreq <= 1500) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS_PLUS]; > > + else > > + return -EINVAL; > > Add a max_hsfreq field to rzg2l_csi2_timings, and write > > for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_global_timings); ++i) { > dphy_timing = &rzg2l_csi2_global_timings[i]; > > if (csi2->hsfreq <= dphy_timing->max_hsfreq) > break; > } > > You can then drop the rzg2l_dphy_timings enum. > Thanks, Ive now implemeted as per your suggestion. > > + > > + /* Set D-PHY timing parameters */ > > + dphytim0 = CSIDPHYTIM0_TCLK_MISS(dphy_timing->tclk_miss) | > > + CSIDPHYTIM0_T_INIT(dphy_timing->t_init); > > + dphytim1 = CSIDPHYTIM1_THS_PREPARE(dphy_timing->ths_prepare) | > > + CSIDPHYTIM1_TCLK_PREPARE(dphy_timing->tclk_prepare) | > > + CSIDPHYTIM1_THS_SETTLE(dphy_timing->ths_settle) | > > + CSIDPHYTIM1_TCLK_SETTLE(dphy_timing->tclk_settle); > > + rzg2l_csi2_write(csi2, CSIDPHYTIM0, dphytim0); > > + rzg2l_csi2_write(csi2, CSIDPHYTIM1, dphytim1); > > + > > + /* Enable D-PHY power control 0 */ > > + rzg2l_csi2_write(csi2, CSIDPHYSKW0, CSIDPHYSKW0_DEFAULT_SKW); > > + > > + /* Set the EN_BGR bit */ > > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > > + > > + /* Delay 20us to be stable */ > > + usleep_range(20, 40); > > + > > + /* Enable D-PHY power control 1 */ > > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > > + > > + /* Delay 10us to be stable */ > > + usleep_range(10, 20); > > + > > + /* Start supplying the internal clock for the D-PHY block */ > > + ret = clk_prepare_enable(csi2->sysclk); > > + if (ret) > > + rzg2l_csi2_dphy_disable(csi2); > > + > > + return ret; > > +} > > + > > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on) > > +{ > > + if (on) > > + return rzg2l_csi2_dphy_enable(csi2); > > + > > + return rzg2l_csi2_dphy_disable(csi2); > > +} > > Does this work without exporting the symbol when the drivers are > compiled as modules ? > No it needs an EXPORT_SYMBOL(). > > + > > +static void rzg2l_csi2_mipi_link_enable(struct rzg2l_csi2 *csi2) > > +{ > > + unsigned long vclk_rate = clk_get_rate(csi2->vclk) / HZ_PER_MHZ; > > + u32 frrskw, frrclk, frrskw_coeff, frrclk_coeff; > > + > > + /* Select data lanes */ > > + rzg2l_csi2_write(csi2, CSI2nMCT0, CSI2nMCT0_VDLN(csi2->lanes)); > > + > > + frrskw_coeff = 3 * vclk_rate * 8; > > + frrclk_coeff = frrskw_coeff / 2; > > + frrskw = DIV_ROUND_UP(frrskw_coeff, csi2->hsfreq); > > + frrclk = DIV_ROUND_UP(frrclk_coeff, csi2->hsfreq); > > + rzg2l_csi2_write(csi2, CSI2nMCT2, CSI2nMCT2_FRRSKW(frrskw) | > > + CSI2nMCT2_FRRCLK(frrclk)); > > + > > + /* > > + * Select data type. > > + * FS, FE, LS, LE, Generic Short Packet Codes 1 to 8, > > + * Generic Long Packet Data Types 1 to 4 YUV422 8-bit, > > + * RGB565, RGB888, RAW8 to RAW20, User-defined 8-bit > > + * data types 1 to 8 > > + */ > > + rzg2l_csi2_write(csi2, CSI2nDTEL, 0xf778ff0f); > > + rzg2l_csi2_write(csi2, CSI2nDTEH, 0x00ffff1f); > > + > > + /* Enable LINK reception */ > > + rzg2l_csi2_write(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > > +} > > + > > +static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) > > +{ > > + unsigned int timeout = VSRSTS_RETRIES; > > + > > + /* Stop LINK reception */ > > + rzg2l_csi2_clr(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > > + > > + /* Request a software reset of the LINK Video Pixel Interface */ > > + rzg2l_csi2_write(csi2, CSI2nRTCT, CSI2nRTCT_VSRST); > > + > > + /* Make sure CSI2nRTST.VSRSTS bit is cleared */ > > + while (timeout--) { > > + if (!(rzg2l_csi2_read(csi2, CSI2nRTST) & CSI2nRTST_VSRSTS)) > > + break; > > + usleep_range(100, 200); > > + }; > > + > > + if (!timeout) > > + dev_err(csi2->dev, "Clearing CSI2nRTST.VSRSTS timed out\n"); > > +} > > + > > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on) > > Can this also be moved to rzg2l_csi2_s_stream() instead of being called > from the CRU driver ? I really dislike the manual calls between the two > drivers. This will mess up future reworks of the subdev infrastructure > :-( Please try to stick to the standard code flow as much as possible. > I am trying to avoid this now based on the suggestion provided by Sakari. > > +{ > > + if (on) > > + rzg2l_csi2_mipi_link_enable(csi2); > > + else > > + rzg2l_csi2_mipi_link_disable(csi2); > > +} > > + > > +static int rzg2l_csi2_s_stream(struct v4l2_subdev *sd, int enable) > > +{ > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); > > + > > + return v4l2_subdev_call(csi2->remote_source, video, s_stream, enable); > > +} > > + > > +static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, > > + struct v4l2_subdev_format *fmt) > > +{ > > + struct v4l2_mbus_framefmt *src_format; > > + struct v4l2_mbus_framefmt *format; > > + > > + if (fmt->pad > RZG2L_CSI2_SINK) > > + return -EINVAL; > > That's not right, set_format must not return an error on the source pad. > Have you run v4l2-compliance on this driver ? Please include the > v4l2-compliance report in the cover letter. > OK, I'll provide the v4l2-compliance report. > As the CSI-2 receiver has the same format on the input and output, you > can simply write > > src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE); > > if (fmt->pad == RZG2L_CSI2_SOURCE) { > fmt->format = *src_format; > return 0; > } > > here. I'd also rename format to sink_format and write > > sink_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); > src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE); > > at the beginning of the function. > Agreed, will do. > > + > > + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); > > + > > + if (!rzg2l_csi2_code_to_fmt(fmt->format.code)) > > + format->code = rzg2l_csi2_formats[0].code; > > + else > > + format->code = fmt->format.code; > > + > > + format->field = V4L2_FIELD_NONE; > > + format->colorspace = V4L2_COLORSPACE_SRGB; > > The CSI-2 receiver doesn't care about color spaces, so I would simply > copy all colorspace-related fields received from userspace (colorspace, > xfer_func, ycbcr_enc and quantization). > OK. > > + format->width = fmt->format.width; > > + format->height = fmt->format.height; > > + fmt->format = *format; > > + > > + /* propagate format to source pad */ > > + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE_VC0); > > + *src_format = *format; > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_init_config(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state) > > +{ > > + struct v4l2_subdev_format fmt = { 0 }; > > + > > + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; > > When using the subdev active state, the state pointer passed to this > function will never be null. That's fine, as rzg2l_csi2_set_format() > doesn't use the which field. I'd just drop this line. > Thanks, I'll drop this line. > Please set the pad to RZG2L_CSI2_SINK explicitly. It works as-is because > RZG2L_CSI2_SINK is equal to zero, and the structure is zeroed above, but > let's not depend on that. > Agreed, will do. > > + fmt.format.width = RZG2L_CSI2_DEFAULT_WIDTH; > > + fmt.format.height = RZG2L_CSI2_DEFAULT_HEIGHT; > > + fmt.format.field = V4L2_FIELD_NONE; > > + fmt.format.code = RZG2L_CSI2_DEFAULT_FMT; > > + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; > > + And I will set the defaults to colorspace, xfer_func, ycbcr_enc and quantization. > > + return rzg2l_csi2_set_format(sd, sd_state, &fmt); > > +} > > + > > +static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_mbus_code_enum *code) > > +{ > > + if (code->index >= ARRAY_SIZE(rzg2l_csi2_formats)) > > + return -EINVAL; > > + > > + code->code = rzg2l_csi2_formats[code->index].code; > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_enum_frame_size(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_frame_size_enum *fse) > > +{ > > + if (fse->index != 0) > > + return -EINVAL; > > + > > + fse->min_width = clamp_t(u32, 1, 320, 2800); > > + fse->min_height = clamp_t(u32, 1, 240, 4095); > > + fse->max_width = clamp_t(u32, -1, 320, 2800); > > + fse->max_height = clamp_t(u32, -1, 240, 4095); > > That's a weird way to write > > fse->min_width = 320; > fse->min_height = 240; > fse->max_width = 2800; > fse->max_height = 4095; > > Could you add macros for those values, the same way you already have > RZG2L_CSI2_DEFAULT_WIDTH and RZG2L_CSI2_DEFAULT_HEIGHT ? > rzg2l_csi2_set_format() should also clamp the width and height > accordingly. > Agreed, will do. > > + > > + return 0; > > +} > > + > > +static const struct v4l2_subdev_video_ops rzg2l_csi2_video_ops = { > > + .s_stream = rzg2l_csi2_s_stream, > > +}; > > + > > +static const struct v4l2_subdev_pad_ops rzg2l_csi2_pad_ops = { > > + .enum_mbus_code = rzg2l_csi2_enum_mbus_code, > > + .init_cfg = rzg2l_csi2_init_config, > > + .enum_frame_size = rzg2l_csi2_enum_frame_size, > > + .set_fmt = rzg2l_csi2_set_format, > > + .get_fmt = v4l2_subdev_get_fmt, > > +}; > > + > > +static const struct v4l2_subdev_ops rzg2l_csi2_subdev_ops = { > > + .video = &rzg2l_csi2_video_ops, > > + .pad = &rzg2l_csi2_pad_ops, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Async handling and registration of subdevices and links. > > + */ > > + > > +static int rzg2l_csi2_notify_bound(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *subdev, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > > + > > + csi2->remote_source = subdev; > > + > > + dev_dbg(csi2->dev, "Bound subdev: %s pad\n", subdev->name); > > + > > + return media_create_pad_link(&subdev->entity, RZG2L_CSI2_SINK, > > + &csi2->subdev.entity, 0, > > + MEDIA_LNK_FL_ENABLED | > > + MEDIA_LNK_FL_IMMUTABLE); > > +} > > + > > +static void rzg2l_csi2_notify_unbind(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *subdev, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > > + > > + csi2->remote_source = NULL; > > + > > + dev_dbg(csi2->dev, "Unbind subdev %s\n", subdev->name); > > +} > > + > > +static const struct v4l2_async_notifier_operations rzg2l_csi2_notify_ops = { > > + .bound = rzg2l_csi2_notify_bound, > > + .unbind = rzg2l_csi2_notify_unbind, > > +}; > > + > > +static int rzg2l_csi2_parse_v4l2(struct rzg2l_csi2 *csi2, > > + struct v4l2_fwnode_endpoint *vep) > > +{ > > + /* Only port 0 endpoint 0 is valid. */ > > + if (vep->base.port || vep->base.id) > > + return -ENOTCONN; > > + > > + csi2->lanes = vep->bus.mipi_csi2.num_data_lanes; > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_parse_dt(struct rzg2l_csi2 *csi2) > > +{ > > + struct v4l2_fwnode_endpoint v4l2_ep = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY > > + }; > > + struct v4l2_async_subdev *asd; > > + struct fwnode_handle *fwnode; > > + struct fwnode_handle *ep; > > + int ret; > > + > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, 0); > > + if (!ep) { > > + dev_err(csi2->dev, "Not connected to subdevice\n"); > > + return -EINVAL; > > + } > > + > > + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); > > + if (ret) { > > + dev_err(csi2->dev, "Could not parse v4l2 endpoint\n"); > > + fwnode_handle_put(ep); > > + return -EINVAL; > > + } > > + > > + ret = rzg2l_csi2_parse_v4l2(csi2, &v4l2_ep); > > + if (ret) { > > + fwnode_handle_put(ep); > > + return ret; > > + } > > + > > + fwnode = fwnode_graph_get_remote_endpoint(ep); > > + fwnode_handle_put(ep); > > + > > + dev_dbg(csi2->dev, "Found '%pOF'\n", to_of_node(fwnode)); > > + > > + v4l2_async_nf_init(&csi2->notifier); > > + csi2->notifier.ops = &rzg2l_csi2_notify_ops; > > + > > + asd = v4l2_async_nf_add_fwnode(&csi2->notifier, fwnode, > > + struct v4l2_async_subdev); > > + fwnode_handle_put(fwnode); > > + if (IS_ERR(asd)) > > + return PTR_ERR(asd); > > + > > + ret = v4l2_async_subdev_nf_register(&csi2->subdev, &csi2->notifier); > > + if (ret) > > + v4l2_async_nf_cleanup(&csi2->notifier); > > + > > + return ret; > > +} > > + > > +static int rzg2l_validate_csi2_lanes(struct rzg2l_csi2 *csi2) > > +{ > > + int ret = 0; > > + int lanes; > > + > > + if (csi2->lanes != 1 && csi2->lanes != 2 && csi2->lanes != 4) { > > + dev_err(csi2->dev, "Unsupported number of data-lanes: %u\n", > > + csi2->lanes); > > + return -EINVAL; > > + } > > + > > + ret = clk_prepare_enable(csi2->pclk); > > + if (ret) > > + return ret; > > + > > + /* Checking the maximum lanes support for CSI-2 module */ > > + lanes = (rzg2l_csi2_read(csi2, CSI2nMCG) & CSI2nMCG_SDLN) >> 8; > > + if (lanes < csi2->lanes) { > > + dev_err(csi2->dev, > > + "Failed to support %d data lanes\n", csi2->lanes); > > + ret = -EINVAL; > > + } > > + > > + clk_disable_unprepare(csi2->pclk); > > + > > + return ret; > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * Platform Device Driver. > > + */ > > + > > +static const struct media_entity_operations rzg2l_csi2_entity_ops = { > > + .link_validate = v4l2_subdev_link_validate, > > +}; > > + > > +static int rzg2l_csi2_probe(struct platform_device *pdev) > > +{ > > + struct rzg2l_csi2 *csi2; > > + int ret; > > + > > + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); > > + if (!csi2) > > + return -ENOMEM; > > + > > + csi2->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(csi2->base)) > > + return PTR_ERR(csi2->base); > > + > > + csi2->rstc = devm_reset_control_get(&pdev->dev, "cmn-rstb"); > > + if (IS_ERR(csi2->rstc)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->rstc), > > + "failed to get cpg cmn-rstb\n"); > > + > > + csi2->sysclk = devm_clk_get(&pdev->dev, "sysclk"); > > + if (IS_ERR(csi2->sysclk)) { > > + dev_err(&pdev->dev, "Failed to get sysclk"); > > + return PTR_ERR(csi2->sysclk); > > + } > > + > > + csi2->vclk = devm_clk_get(&pdev->dev, "vclk"); > > + if (IS_ERR(csi2->vclk)) { > > + dev_err(&pdev->dev, "Failed to get vclk"); > > + return PTR_ERR(csi2->vclk); > > + } > > + > > + csi2->pclk = devm_clk_get(&pdev->dev, "pclk"); > > + if (IS_ERR(csi2->pclk)) { > > + dev_err(&pdev->dev, "Failed to get pclk"); > > + return PTR_ERR(csi2->pclk); > > + } > > Could devm_clk_bulk_get() help ? > Enabling/disabling specific clocks would be more code? > > + > > + csi2->dev = &pdev->dev; > > + > > + platform_set_drvdata(pdev, csi2); > > + > > + ret = rzg2l_csi2_parse_dt(csi2); > > + if (ret) > > + return ret; > > + > > + ret = rzg2l_validate_csi2_lanes(csi2); > > + if (ret) > > + return ret; > > + > > + csi2->subdev.dev = &pdev->dev; > > + v4l2_subdev_init(&csi2->subdev, &rzg2l_csi2_subdev_ops); > > + v4l2_set_subdevdata(&csi2->subdev, &pdev->dev); > > + snprintf(csi2->subdev.name, sizeof(csi2->subdev.name), > > + "csi-%s", dev_name(&pdev->dev)); > > + csi2->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; > > + > > + csi2->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > > + csi2->subdev.entity.ops = &rzg2l_csi2_entity_ops; > > + > > + csi2->pads[RZG2L_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; > > + /* > > + * TODO: RZ/G2L CSI2 supports 4 virtual channels, as virtual > > + * channels should be implemented by streams API which is under > > + * development lets hardcode to VC0 for now. > > + */ > > + csi2->pads[RZG2L_CSI2_SOURCE_VC0].flags = MEDIA_PAD_FL_SOURCE; > > + ret = media_entity_pads_init(&csi2->subdev.entity, 2, csi2->pads); > > + if (ret) > > + goto error_async; > > + > > + pm_runtime_enable(&pdev->dev); > > + > > + ret = v4l2_subdev_init_finalize(&csi2->subdev); > > + if (ret < 0) > > + goto error_pm; > > + > > + /* enable pclk for register access */ > > + ret = clk_prepare_enable(csi2->pclk); > > + if (ret) > > + goto error_pm; > > Keeping the clock always on isn't great from a power management point of > view. Runtime PM would be much better. > I have now moved clk_prepare_enable()/clk_disable_unprepare() this to rzg2l_csi2_dphy_setting() and rzg2l_csi2_mipi_link_setting() thats where the actual read/writes are happening. > > + > > + ret = v4l2_async_register_subdev(&csi2->subdev); > > + if (ret < 0) > > + goto error_subdev; > > + > > + dev_info(csi2->dev, "%d lanes found\n", csi2->lanes); > > + > > + return 0; > > + > > +error_subdev: > > + clk_disable_unprepare(csi2->pclk); > > + v4l2_subdev_cleanup(&csi2->subdev); > > +error_pm: > > + pm_runtime_disable(&pdev->dev); > > +error_async: > > + v4l2_async_nf_unregister(&csi2->notifier); > > + v4l2_async_nf_cleanup(&csi2->notifier); > > + > > + return ret; > > +} > > + > > +static const struct of_device_id rzg2l_csi2_of_table[] = { > > + { .compatible = "renesas,rzg2l-csi2", }, > > + { /* sentinel */ } > > +}; > > + > > +static int rzg2l_csi2_remove(struct platform_device *pdev) > > +{ > > + struct rzg2l_csi2 *csi2 = platform_get_drvdata(pdev); > > + > > + v4l2_async_nf_unregister(&csi2->notifier); > > + v4l2_async_nf_cleanup(&csi2->notifier); > > + v4l2_async_unregister_subdev(&csi2->subdev); > > + clk_disable_unprepare(csi2->pclk); > > + v4l2_subdev_cleanup(&csi2->subdev); > > + > > + pm_runtime_disable(&pdev->dev); > > + > > + return 0; > > +} > > + > > +static struct platform_driver rzg2l_csi2_pdrv = { > > + .remove = rzg2l_csi2_remove, > > + .probe = rzg2l_csi2_probe, > > + .driver = { > > + .name = "rzg2l-csi2", > > + .of_match_table = rzg2l_csi2_of_table, > > + }, > > +}; > > + > > +module_platform_driver(rzg2l_csi2_pdrv); > > + > > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > > +MODULE_DESCRIPTION("Renesas RZ/G2L MIPI CSI2 receiver driver"); > > +MODULE_LICENSE("GPL"); > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > new file mode 100644 > > index 000000000000..f85a1d44250f > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > @@ -0,0 +1,46 @@ > > +/* SPDX-License-Identifier: GPL-2.0+ */ > > +/* > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + */ > > + > > +#ifndef __RZG2L_CSI2__ > > +#define __RZG2L_CSI2__ > > + > > +enum rzg2l_csi2_pads { > > + RZG2L_CSI2_SINK = 0, > > + RZG2L_CSI2_SOURCE_VC0, > > + RZG2L_CSI2_SOURCE_VC1, > > + RZG2L_CSI2_SOURCE_VC2, > > + RZG2L_CSI2_SOURCE_VC3, > > Drop RZG2L_CSI2_SOURCE_VC[123] and rename RZG2L_CSI2_SOURCE_VC0 to > RZG2L_CSI2_SOURCE. > Done. > > + NR_OF_RZG2L_CSI2_PAD, > > +}; > > + > > +struct rzg2l_csi2 { > > + struct device *dev; > > + void __iomem *base; > > + struct reset_control *rstc; > > + struct clk *sysclk; > > + struct clk *vclk; > > + struct clk *pclk; > > + > > + struct v4l2_subdev subdev; > > + struct media_pad pads[NR_OF_RZG2L_CSI2_PAD]; > > + > > + struct v4l2_async_notifier notifier; > > + struct v4l2_subdev *remote_source; > > + > > + unsigned short lanes; > > + > > + unsigned long hsfreq; > > +}; > > + > > +static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) > > +{ > > + return container_of(sd, struct rzg2l_csi2, subdev); > > +} > > + > > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2); > > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on); > > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on); > > Let's make the API as opaque as possible for the CRU driver. I'd move > the definition of the rzg2l_csi2 structure to the .c file, along with > the sd_to_csi2() function, and change these three functions to take a > v4l2_subdev pointer. > Agreed, I have got rid of the above three functions (infact the header file itself is removed) and now using the pre_streamon callback. (Initial testing looks good) Cheers, Prabhakar
Hi Prabhakar, On Thu, Sep 22, 2022 at 01:08:33PM +0100, Lad, Prabhakar wrote: > > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > > the mipi/dhpy initialization happens as per the HW manual > > > > That doesn't look right. The driver doesn't use runtime PM anymore, so > > power domains may not be handled properly. What was the problem with > > clock handling using runtime PM ? > > > If we use the runtime PM all the clocks will be turned ON when we call > pm_runtime_resume_and_get() which I dont want to. As per the "Starting > reception for MIPI CSI-2 Input" section 35.3.1 for example we first > need to turn ON all the clocks and later further down the line we need > to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such > cases I have switched to individual clock handling. If that is the case, then you should control just that clock directly, outside runtime PM callbacks. Runtime PM may be needed e.g. for resuming a parent device.
On Thu, Sep 22, 2022 at 2:34 PM Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > On Thu, Sep 22, 2022 at 01:08:33PM +0100, Lad, Prabhakar wrote: > > > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > > > the mipi/dhpy initialization happens as per the HW manual > > > > > > That doesn't look right. The driver doesn't use runtime PM anymore, so > > > power domains may not be handled properly. What was the problem with > > > clock handling using runtime PM ? > > > > > If we use the runtime PM all the clocks will be turned ON when we call > > pm_runtime_resume_and_get() which I dont want to. As per the "Starting > > reception for MIPI CSI-2 Input" section 35.3.1 for example we first > > need to turn ON all the clocks and later further down the line we need > > to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such > > cases I have switched to individual clock handling. > > If that is the case, then you should control just that clock directly, > outside runtime PM callbacks. > > Runtime PM may be needed e.g. for resuming a parent device. Exactly. So probably you should not consider R9A07G044_CRU_VCLK a PM clock, i.e. you need changes to rzg2l_cpg_is_pm_clk() to exclude it. 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
Hi Sakari, Thank you for the review. On Wed, Sep 21, 2022 at 10:35 PM Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > > Hi Prabhakar, > > Thanks for the set. > > On Tue, Sep 06, 2022 at 12:04:05AM +0100, Lad Prabhakar wrote: > > Add MIPI CSI-2 receiver driver for Renesas RZ/G2L. The MIPI > > CSI-2 is part of the CRU module found on RZ/G2L family. > > > > Based on a patch in the BSP by Hien Huynh > > <hien.huynh.px@renesas.com> > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > --- > > v1 -> v2 > > * Sorted Kconfig select > > * Prefixed generic names for struct/variables with rzg2_csi2 > > * Dropped unnecessary checks for remote source > > * Dropped exporting functions > > * Moved lane validation to probe > > * Split up rzg2l_csi2_dphy_setting() and rzg2l_csi2_mipi_link_setting() > > * Used rzg2l_csi2_write() wherever possible > > * Dropped stream_count/lock > > * Used active subdev state instead of manually storing format in driver > > * Implemented init_cfg/enum_frame_size/enum_mbus_code callbacks > > * Dropped check for bus_type of remote source > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > the mipi/dhpy initialization happens as per the HW manual > > * Hardcoded VC0 usage for now as streams API is under development > > > > RFC v2 -> v1 > > * Fixed initialization sequence of DPHY and link > > * Exported DPHY and link initialization functions so that the > > CRU core driver can initialize the CRU and CSI2 as per the HW manual. > > > > RFC v1 -> RFC v2 > > * new patch (split up as new driver compared to v1) > > --- > > drivers/media/platform/renesas/Kconfig | 1 + > > drivers/media/platform/renesas/Makefile | 1 + > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 761 ++++++++++++++++++ > > .../platform/renesas/rzg2l-cru/rzg2l-csi2.h | 46 ++ > > 6 files changed, 829 insertions(+) > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Kconfig > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/Makefile > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > > > diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig > > index 9fd90672ea2d..ed788e991f74 100644 > > --- a/drivers/media/platform/renesas/Kconfig > > +++ b/drivers/media/platform/renesas/Kconfig > > @@ -41,6 +41,7 @@ config VIDEO_SH_VOU > > Support for the Video Output Unit (VOU) on SuperH SoCs. > > > > source "drivers/media/platform/renesas/rcar-vin/Kconfig" > > +source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" > > > > # Mem2mem drivers > > > > diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile > > index 3ec226ef5fd2..55854e868887 100644 > > --- a/drivers/media/platform/renesas/Makefile > > +++ b/drivers/media/platform/renesas/Makefile > > @@ -4,6 +4,7 @@ > > # > > > > obj-y += rcar-vin/ > > +obj-y += rzg2l-cru/ > > obj-y += vsp1/ > > > > obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > new file mode 100644 > > index 000000000000..57c40bb499df > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > @@ -0,0 +1,17 @@ > > +# SPDX-License-Identifier: GPL-2.0 > > + > > +config VIDEO_RZG2L_CSI2 > > + tristate "RZ/G2L MIPI CSI-2 Receiver" > > + depends on ARCH_RENESAS || COMPILE_TEST > > + depends on V4L_PLATFORM_DRIVERS > > + depends on VIDEO_DEV && OF > > + select MEDIA_CONTROLLER > > + select RESET_CONTROLLER > > + select V4L2_FWNODE > > + select VIDEO_V4L2_SUBDEV_API > > + help > > + Support for Renesas RZ/G2L (and alike SoC's) MIPI CSI-2 > > + Receiver driver. > > + > > + To compile this driver as a module, choose M here: the > > + module will be called rzg2l-csi2. > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > new file mode 100644 > > index 000000000000..91ea97a944e6 > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > @@ -0,0 +1,3 @@ > > +# SPDX-License-Identifier: GPL-2.0 > > + > > +obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > > new file mode 100644 > > index 000000000000..1f6838ed64fc > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c > > @@ -0,0 +1,761 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Driver for Renesas RZ/G2L MIPI CSI-2 Receiver > > + * > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + */ > > + > > +#include <linux/clk.h> > > +#include <linux/delay.h> > > +#include <linux/interrupt.h> > > +#include <linux/io.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/of_graph.h> > > +#include <linux/platform_device.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/reset.h> > > +#include <linux/sys_soc.h> > > +#include <linux/units.h> > > + > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-device.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mc.h> > > +#include <media/v4l2-subdev.h> > > + > > +#include "rzg2l-csi2.h" > > + > > +/* LINK registers */ > > +/* Module Configuration Register */ > > +#define CSI2nMCG 0x0 > > +#define CSI2nMCG_SDLN GENMASK(11, 8) > > + > > +/* Module Control Register 0 */ > > +#define CSI2nMCT0 0x10 > > +#define CSI2nMCT0_VDLN(x) ((x) << 0) > > + > > +/* Module Control Register 2 */ > > +#define CSI2nMCT2 0x18 > > +#define CSI2nMCT2_FRRSKW(x) ((x) << 16) > > +#define CSI2nMCT2_FRRCLK(x) ((x) << 0) > > + > > +/* Module Control Register 3 */ > > +#define CSI2nMCT3 0x1c > > +#define CSI2nMCT3_RXEN BIT(0) > > + > > +/* Reset Control Register */ > > +#define CSI2nRTCT 0x28 > > +#define CSI2nRTCT_VSRST BIT(0) > > + > > +/* Reset Status Register */ > > +#define CSI2nRTST 0x2c > > +#define CSI2nRTST_VSRSTS BIT(0) > > + > > +/* Receive Data Type Enable Low Register */ > > +#define CSI2nDTEL 0x60 > > + > > +/* Receive Data Type Enable High Register */ > > +#define CSI2nDTEH 0x64 > > + > > +/* Power Management Status Register */ > > +#define CSI2nPMST 0x200 > > + > > +/* Power Management Status Clear Register */ > > +#define CSI2nPMSC 0x204 > > + > > +/* DPHY registers */ > > +/* D-PHY Control Register 0 */ > > +#define CSIDPHYCTRL0 0x400 > > +#define CSIDPHYCTRL0_EN_LDO1200 BIT(1) > > +#define CSIDPHYCTRL0_EN_BGR BIT(0) > > + > > +/* D-PHY Timing Register 0 */ > > +#define CSIDPHYTIM0 0x404 > > +#define CSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) > > +#define CSIDPHYTIM0_T_INIT(x) ((x) << 0) > > + > > +/* D-PHY Timing Register 1 */ > > +#define CSIDPHYTIM1 0x408 > > +#define CSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) > > +#define CSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) > > +#define CSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) > > +#define CSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) > > + > > +/* D-PHY Skew Adjustment Function */ > > +#define CSIDPHYSKW0 0x460 > > +#define CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(x) ((x) & 0x3) > > +#define CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(x) (((x) & 0x3) << 4) > > +#define CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(x) (((x) & 0x3) << 8) > > +#define CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(x) (((x) & 0x3) << 12) > > +#define CSIDPHYSKW0_DEFAULT_SKW CSIDPHYSKW0_UTIL_DL0_SKW_ADJ(1) | \ > > + CSIDPHYSKW0_UTIL_DL1_SKW_ADJ(1) | \ > > + CSIDPHYSKW0_UTIL_DL2_SKW_ADJ(1) | \ > > + CSIDPHYSKW0_UTIL_DL3_SKW_ADJ(1) > > + > > +#define VSRSTS_RETRIES 20 > > + > > +#define RZG2L_CSI2_DEFAULT_WIDTH 800 > > +#define RZG2L_CSI2_DEFAULT_HEIGHT 600 > > +#define RZG2L_CSI2_DEFAULT_FMT MEDIA_BUS_FMT_UYVY8_1X16 > > + > > +struct rzg2l_csi2_timings { > > + u32 t_init; > > + u32 tclk_miss; > > + u32 tclk_settle; > > + u32 ths_settle; > > + u32 tclk_prepare; > > + u32 ths_prepare; > > +}; > > + > > +enum rzg2l_dphy_timings { > > + TRANSMISSION_RATE_80_MBPS = 0, > > + TRANSMISSION_RATE_125_MBPS, > > + TRANSMISSION_RATE_250_MBPS, > > + TRANSMISSION_RATE_360_MBPS, > > + TRANSMISSION_RATE_360_MBPS_PLUS, > > +}; > > + > > +static const struct rzg2l_csi2_timings rzg2l_csi2_global_timings[] = { > > + [TRANSMISSION_RATE_80_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 23, > > + .ths_settle = 31, > > + .tclk_prepare = 10, > > + .ths_prepare = 19, > > + }, > > + [TRANSMISSION_RATE_125_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 23, > > + .ths_settle = 28, > > + .tclk_prepare = 10, > > + .ths_prepare = 19, > > + }, > > + [TRANSMISSION_RATE_250_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 23, > > + .ths_settle = 22, > > + .tclk_prepare = 10, > > + .ths_prepare = 16, > > + }, > > + [TRANSMISSION_RATE_360_MBPS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 18, > > + .ths_settle = 19, > > + .tclk_prepare = 10, > > + .ths_prepare = 10, > > + }, > > + [TRANSMISSION_RATE_360_MBPS_PLUS] = { > > + .t_init = 79801, > > + .tclk_miss = 4, > > + .tclk_settle = 18, > > + .ths_settle = 18, > > + .tclk_prepare = 10, > > + .ths_prepare = 10, > > + }, > > +}; > > + > > +struct rzg2l_csi2_format { > > + u32 code; > > + unsigned int datatype; > > + unsigned int bpp; > > +}; > > + > > +static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { > > + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, > > +}; > > + > > +static const struct rzg2l_csi2_format *rzg2l_csi2_code_to_fmt(unsigned int code) > > +{ > > + unsigned int i; > > + > > + for (i = 0; i < ARRAY_SIZE(rzg2l_csi2_formats); i++) > > + if (rzg2l_csi2_formats[i].code == code) > > + return &rzg2l_csi2_formats[i]; > > + > > + return NULL; > > +} > > + > > +static inline struct rzg2l_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) > > +{ > > + return container_of(n, struct rzg2l_csi2, notifier); > > +} > > + > > +static u32 rzg2l_csi2_read(struct rzg2l_csi2 *csi2, unsigned int reg) > > +{ > > + return ioread32(csi2->base + reg); > > +} > > + > > +static void rzg2l_csi2_write(struct rzg2l_csi2 *csi2, unsigned int reg, > > + u32 data) > > +{ > > + iowrite32(data, csi2->base + reg); > > +} > > + > > +static void rzg2l_csi2_set(struct rzg2l_csi2 *csi2, unsigned int reg, u32 set) > > +{ > > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) | set); > > +} > > + > > +static void rzg2l_csi2_clr(struct rzg2l_csi2 *csi2, unsigned int reg, u32 clr) > > +{ > > + rzg2l_csi2_write(csi2, reg, rzg2l_csi2_read(csi2, reg) & ~clr); > > +} > > + > > +static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) > > +{ > > + struct v4l2_subdev *sd = csi2->remote_source; > > + const struct rzg2l_csi2_format *format; > > + const struct v4l2_mbus_framefmt *fmt; > > + struct v4l2_subdev_state *state; > > + struct v4l2_ctrl *ctrl; > > + u64 mbps; > > + > > + /* Read the pixel rate control from remote. */ > > + ctrl = v4l2_ctrl_find(sd->ctrl_handler, V4L2_CID_PIXEL_RATE); > > + if (!ctrl) { > > + dev_err(csi2->dev, "no pixel rate control in subdev %s\n", > > + sd->name); > > + return -EINVAL; > > + } > > + > > + state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); > > + fmt = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SINK); > > + v4l2_subdev_unlock_state(state); > > + format = rzg2l_csi2_code_to_fmt(fmt->code); > > You shouldn't access fmt once you've unlocked subdev state. > OK, I'll move the line above the unlock. > > + > > + /* > > + * Calculate hsfreq in Mbps > > + * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes > > + */ > > + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; > > + do_div(mbps, csi2->lanes * 1000000); > > + > > + return mbps; > > +} > > + > > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2) > > +{ > > + return reset_control_deassert(csi2->rstc); > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * DPHY setting > > + */ > > + > > +static int rzg2l_csi2_dphy_disable(struct rzg2l_csi2 *csi2) > > +{ > > + int ret; > > + > > + /* Reset the CRU (D-PHY) */ > > + ret = reset_control_assert(csi2->rstc); > > + if (ret) > > + return ret; > > + > > + /* Stop the D-PHY clock */ > > + clk_disable_unprepare(csi2->sysclk); > > + > > + /* Cancel the EN_LDO1200 register setting */ > > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > > + > > + /* Cancel the EN_BGR register setting */ > > + rzg2l_csi2_clr(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_dphy_enable(struct rzg2l_csi2 *csi2) > > +{ > > + const struct rzg2l_csi2_timings *dphy_timing; > > + u32 dphytim0, dphytim1; > > + int mbps; > > + int ret; > > + > > + mbps = rzg2l_csi2_calc_mbps(csi2); > > + if (mbps < 0) > > + return mbps; > > + > > + csi2->hsfreq = mbps; > > + > > + /* Set DPHY timing parameters */ > > + if (csi2->hsfreq <= 80) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_80_MBPS]; > > + else if (csi2->hsfreq <= 125) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_125_MBPS]; > > + else if (csi2->hsfreq <= 250) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_250_MBPS]; > > + else if (csi2->hsfreq <= 360) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS]; > > + else if (csi2->hsfreq <= 1500) > > + dphy_timing = &rzg2l_csi2_global_timings[TRANSMISSION_RATE_360_MBPS_PLUS]; > > + else > > + return -EINVAL; > > + > > + /* Set D-PHY timing parameters */ > > + dphytim0 = CSIDPHYTIM0_TCLK_MISS(dphy_timing->tclk_miss) | > > + CSIDPHYTIM0_T_INIT(dphy_timing->t_init); > > + dphytim1 = CSIDPHYTIM1_THS_PREPARE(dphy_timing->ths_prepare) | > > + CSIDPHYTIM1_TCLK_PREPARE(dphy_timing->tclk_prepare) | > > + CSIDPHYTIM1_THS_SETTLE(dphy_timing->ths_settle) | > > + CSIDPHYTIM1_TCLK_SETTLE(dphy_timing->tclk_settle); > > + rzg2l_csi2_write(csi2, CSIDPHYTIM0, dphytim0); > > + rzg2l_csi2_write(csi2, CSIDPHYTIM1, dphytim1); > > + > > + /* Enable D-PHY power control 0 */ > > + rzg2l_csi2_write(csi2, CSIDPHYSKW0, CSIDPHYSKW0_DEFAULT_SKW); > > + > > + /* Set the EN_BGR bit */ > > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_BGR); > > + > > + /* Delay 20us to be stable */ > > + usleep_range(20, 40); > > + > > + /* Enable D-PHY power control 1 */ > > + rzg2l_csi2_set(csi2, CSIDPHYCTRL0, CSIDPHYCTRL0_EN_LDO1200); > > + > > + /* Delay 10us to be stable */ > > + usleep_range(10, 20); > > + > > + /* Start supplying the internal clock for the D-PHY block */ > > + ret = clk_prepare_enable(csi2->sysclk); > > + if (ret) > > + rzg2l_csi2_dphy_disable(csi2); > > + > > + return ret; > > +} > > + > > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on) > > +{ > > + if (on) > > + return rzg2l_csi2_dphy_enable(csi2); > > + > > + return rzg2l_csi2_dphy_disable(csi2); > > +} > > + > > +static void rzg2l_csi2_mipi_link_enable(struct rzg2l_csi2 *csi2) > > +{ > > + unsigned long vclk_rate = clk_get_rate(csi2->vclk) / HZ_PER_MHZ; > > + u32 frrskw, frrclk, frrskw_coeff, frrclk_coeff; > > + > > + /* Select data lanes */ > > + rzg2l_csi2_write(csi2, CSI2nMCT0, CSI2nMCT0_VDLN(csi2->lanes)); > > + > > + frrskw_coeff = 3 * vclk_rate * 8; > > + frrclk_coeff = frrskw_coeff / 2; > > + frrskw = DIV_ROUND_UP(frrskw_coeff, csi2->hsfreq); > > + frrclk = DIV_ROUND_UP(frrclk_coeff, csi2->hsfreq); > > + rzg2l_csi2_write(csi2, CSI2nMCT2, CSI2nMCT2_FRRSKW(frrskw) | > > + CSI2nMCT2_FRRCLK(frrclk)); > > + > > + /* > > + * Select data type. > > + * FS, FE, LS, LE, Generic Short Packet Codes 1 to 8, > > + * Generic Long Packet Data Types 1 to 4 YUV422 8-bit, > > + * RGB565, RGB888, RAW8 to RAW20, User-defined 8-bit > > + * data types 1 to 8 > > + */ > > + rzg2l_csi2_write(csi2, CSI2nDTEL, 0xf778ff0f); > > + rzg2l_csi2_write(csi2, CSI2nDTEH, 0x00ffff1f); > > + > > + /* Enable LINK reception */ > > + rzg2l_csi2_write(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > > +} > > + > > +static void rzg2l_csi2_mipi_link_disable(struct rzg2l_csi2 *csi2) > > +{ > > + unsigned int timeout = VSRSTS_RETRIES; > > + > > + /* Stop LINK reception */ > > + rzg2l_csi2_clr(csi2, CSI2nMCT3, CSI2nMCT3_RXEN); > > + > > + /* Request a software reset of the LINK Video Pixel Interface */ > > + rzg2l_csi2_write(csi2, CSI2nRTCT, CSI2nRTCT_VSRST); > > + > > + /* Make sure CSI2nRTST.VSRSTS bit is cleared */ > > + while (timeout--) { > > + if (!(rzg2l_csi2_read(csi2, CSI2nRTST) & CSI2nRTST_VSRSTS)) > > + break; > > + usleep_range(100, 200); > > + }; > > + > > + if (!timeout) > > + dev_err(csi2->dev, "Clearing CSI2nRTST.VSRSTS timed out\n"); > > +} > > + > > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on) > > +{ > > + if (on) > > + rzg2l_csi2_mipi_link_enable(csi2); > > + else > > + rzg2l_csi2_mipi_link_disable(csi2); > > +} > > + > > +static int rzg2l_csi2_s_stream(struct v4l2_subdev *sd, int enable) > > +{ > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(sd); > > + > > + return v4l2_subdev_call(csi2->remote_source, video, s_stream, enable); > > +} > > + > > +static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, > > + struct v4l2_subdev_format *fmt) > > +{ > > + struct v4l2_mbus_framefmt *src_format; > > + struct v4l2_mbus_framefmt *format; > > + > > + if (fmt->pad > RZG2L_CSI2_SINK) > > + return -EINVAL; > > + > > + format = v4l2_subdev_get_pad_format(sd, state, fmt->pad); > > + > > + if (!rzg2l_csi2_code_to_fmt(fmt->format.code)) > > + format->code = rzg2l_csi2_formats[0].code; > > + else > > + format->code = fmt->format.code; > > + > > + format->field = V4L2_FIELD_NONE; > > + format->colorspace = V4L2_COLORSPACE_SRGB; > > + format->width = fmt->format.width; > > + format->height = fmt->format.height; > > + fmt->format = *format; > > + > > + /* propagate format to source pad */ > > + src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CSI2_SOURCE_VC0); > > + *src_format = *format; > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_init_config(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state) > > +{ > > + struct v4l2_subdev_format fmt = { 0 }; > > You could do this in variable initialisation. > Agreed, I will set the pad here. > > + > > + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; > > + fmt.format.width = RZG2L_CSI2_DEFAULT_WIDTH; > > + fmt.format.height = RZG2L_CSI2_DEFAULT_HEIGHT; > > + fmt.format.field = V4L2_FIELD_NONE; > > + fmt.format.code = RZG2L_CSI2_DEFAULT_FMT; > > + fmt.format.colorspace = V4L2_COLORSPACE_SRGB; > > + > > + return rzg2l_csi2_set_format(sd, sd_state, &fmt); > > +} > > + > > +static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_mbus_code_enum *code) > > +{ > > + if (code->index >= ARRAY_SIZE(rzg2l_csi2_formats)) > > + return -EINVAL; > > + > > + code->code = rzg2l_csi2_formats[code->index].code; > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_enum_frame_size(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_frame_size_enum *fse) > > +{ > > + if (fse->index != 0) > > + return -EINVAL; > > + > > + fse->min_width = clamp_t(u32, 1, 320, 2800); > > + fse->min_height = clamp_t(u32, 1, 240, 4095); > > + fse->max_width = clamp_t(u32, -1, 320, 2800); > > + fse->max_height = clamp_t(u32, -1, 240, 4095); > > + > > + return 0; > > +} > > + > > +static const struct v4l2_subdev_video_ops rzg2l_csi2_video_ops = { > > + .s_stream = rzg2l_csi2_s_stream, > > +}; > > + > > +static const struct v4l2_subdev_pad_ops rzg2l_csi2_pad_ops = { > > + .enum_mbus_code = rzg2l_csi2_enum_mbus_code, > > + .init_cfg = rzg2l_csi2_init_config, > > + .enum_frame_size = rzg2l_csi2_enum_frame_size, > > + .set_fmt = rzg2l_csi2_set_format, > > + .get_fmt = v4l2_subdev_get_fmt, > > +}; > > + > > +static const struct v4l2_subdev_ops rzg2l_csi2_subdev_ops = { > > + .video = &rzg2l_csi2_video_ops, > > + .pad = &rzg2l_csi2_pad_ops, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Async handling and registration of subdevices and links. > > + */ > > + > > +static int rzg2l_csi2_notify_bound(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *subdev, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > > + > > + csi2->remote_source = subdev; > > + > > + dev_dbg(csi2->dev, "Bound subdev: %s pad\n", subdev->name); > > + > > + return media_create_pad_link(&subdev->entity, RZG2L_CSI2_SINK, > > + &csi2->subdev.entity, 0, > > + MEDIA_LNK_FL_ENABLED | > > + MEDIA_LNK_FL_IMMUTABLE); > > +} > > + > > +static void rzg2l_csi2_notify_unbind(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *subdev, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct rzg2l_csi2 *csi2 = notifier_to_csi2(notifier); > > + > > + csi2->remote_source = NULL; > > + > > + dev_dbg(csi2->dev, "Unbind subdev %s\n", subdev->name); > > +} > > + > > +static const struct v4l2_async_notifier_operations rzg2l_csi2_notify_ops = { > > + .bound = rzg2l_csi2_notify_bound, > > + .unbind = rzg2l_csi2_notify_unbind, > > +}; > > + > > +static int rzg2l_csi2_parse_v4l2(struct rzg2l_csi2 *csi2, > > + struct v4l2_fwnode_endpoint *vep) > > +{ > > + /* Only port 0 endpoint 0 is valid. */ > > + if (vep->base.port || vep->base.id) > > + return -ENOTCONN; > > + > > + csi2->lanes = vep->bus.mipi_csi2.num_data_lanes; > > + > > + return 0; > > +} > > + > > +static int rzg2l_csi2_parse_dt(struct rzg2l_csi2 *csi2) > > +{ > > + struct v4l2_fwnode_endpoint v4l2_ep = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY > > + }; > > + struct v4l2_async_subdev *asd; > > + struct fwnode_handle *fwnode; > > + struct fwnode_handle *ep; > > + int ret; > > + > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, 0); > > + if (!ep) { > > + dev_err(csi2->dev, "Not connected to subdevice\n"); > > + return -EINVAL; > > + } > > + > > + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); > > + if (ret) { > > + dev_err(csi2->dev, "Could not parse v4l2 endpoint\n"); > > + fwnode_handle_put(ep); > > + return -EINVAL; > > + } > > + > > + ret = rzg2l_csi2_parse_v4l2(csi2, &v4l2_ep); > > + if (ret) { > > + fwnode_handle_put(ep); > > + return ret; > > + } > > + > > + fwnode = fwnode_graph_get_remote_endpoint(ep); > > + fwnode_handle_put(ep); > > + > > + dev_dbg(csi2->dev, "Found '%pOF'\n", to_of_node(fwnode)); > > You can enable debug message printing in v4l2 fwnode, and sysfs now has > some, too. I'd suggest dropping this line . > Agreed, I will drop it. > > + > > + v4l2_async_nf_init(&csi2->notifier); > > + csi2->notifier.ops = &rzg2l_csi2_notify_ops; > > + > > + asd = v4l2_async_nf_add_fwnode(&csi2->notifier, fwnode, > > + struct v4l2_async_subdev); > > + fwnode_handle_put(fwnode); > > + if (IS_ERR(asd)) > > + return PTR_ERR(asd); > > + > > + ret = v4l2_async_subdev_nf_register(&csi2->subdev, &csi2->notifier); > > + if (ret) > > + v4l2_async_nf_cleanup(&csi2->notifier); > > + > > + return ret; > > +} > > + > > +static int rzg2l_validate_csi2_lanes(struct rzg2l_csi2 *csi2) > > +{ > > + int ret = 0; > > + int lanes; > > + > > + if (csi2->lanes != 1 && csi2->lanes != 2 && csi2->lanes != 4) { > > + dev_err(csi2->dev, "Unsupported number of data-lanes: %u\n", > > + csi2->lanes); > > + return -EINVAL; > > + } > > + > > + ret = clk_prepare_enable(csi2->pclk); > > + if (ret) > > + return ret; > > + > > + /* Checking the maximum lanes support for CSI-2 module */ > > + lanes = (rzg2l_csi2_read(csi2, CSI2nMCG) & CSI2nMCG_SDLN) >> 8; > > + if (lanes < csi2->lanes) { > > + dev_err(csi2->dev, > > + "Failed to support %d data lanes\n", csi2->lanes); > > + ret = -EINVAL; > > + } > > + > > + clk_disable_unprepare(csi2->pclk); > > + > > + return ret; > > +} > > + > > +/* ----------------------------------------------------------------------------- > > + * Platform Device Driver. > > + */ > > + > > +static const struct media_entity_operations rzg2l_csi2_entity_ops = { > > + .link_validate = v4l2_subdev_link_validate, > > +}; > > + > > +static int rzg2l_csi2_probe(struct platform_device *pdev) > > +{ > > + struct rzg2l_csi2 *csi2; > > + int ret; > > + > > + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); > > + if (!csi2) > > + return -ENOMEM; > > + > > + csi2->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(csi2->base)) > > + return PTR_ERR(csi2->base); > > + > > + csi2->rstc = devm_reset_control_get(&pdev->dev, "cmn-rstb"); > > + if (IS_ERR(csi2->rstc)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(csi2->rstc), > > + "failed to get cpg cmn-rstb\n"); > > + > > + csi2->sysclk = devm_clk_get(&pdev->dev, "sysclk"); > > + if (IS_ERR(csi2->sysclk)) { > > + dev_err(&pdev->dev, "Failed to get sysclk"); > > + return PTR_ERR(csi2->sysclk); > > + } > > + > > + csi2->vclk = devm_clk_get(&pdev->dev, "vclk"); > > + if (IS_ERR(csi2->vclk)) { > > + dev_err(&pdev->dev, "Failed to get vclk"); > > + return PTR_ERR(csi2->vclk); > > + } > > + > > + csi2->pclk = devm_clk_get(&pdev->dev, "pclk"); > > + if (IS_ERR(csi2->pclk)) { > > + dev_err(&pdev->dev, "Failed to get pclk"); > > + return PTR_ERR(csi2->pclk); > > + } > > + > > + csi2->dev = &pdev->dev; > > + > > + platform_set_drvdata(pdev, csi2); > > + > > + ret = rzg2l_csi2_parse_dt(csi2); > > + if (ret) > > + return ret; > > + > > + ret = rzg2l_validate_csi2_lanes(csi2); > > + if (ret) > > + return ret; > > + > > + csi2->subdev.dev = &pdev->dev; > > + v4l2_subdev_init(&csi2->subdev, &rzg2l_csi2_subdev_ops); > > + v4l2_set_subdevdata(&csi2->subdev, &pdev->dev); > > + snprintf(csi2->subdev.name, sizeof(csi2->subdev.name), > > + "csi-%s", dev_name(&pdev->dev)); > > + csi2->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; > > + > > + csi2->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > > + csi2->subdev.entity.ops = &rzg2l_csi2_entity_ops; > > + > > + csi2->pads[RZG2L_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; > > + /* > > + * TODO: RZ/G2L CSI2 supports 4 virtual channels, as virtual > > + * channels should be implemented by streams API which is under > > + * development lets hardcode to VC0 for now. > > + */ > > + csi2->pads[RZG2L_CSI2_SOURCE_VC0].flags = MEDIA_PAD_FL_SOURCE; > > + ret = media_entity_pads_init(&csi2->subdev.entity, 2, csi2->pads); > > + if (ret) > > + goto error_async; > > + > > + pm_runtime_enable(&pdev->dev); > > + > > + ret = v4l2_subdev_init_finalize(&csi2->subdev); > > + if (ret < 0) > > + goto error_pm; > > + > > + /* enable pclk for register access */ > > + ret = clk_prepare_enable(csi2->pclk); > > + if (ret) > > + goto error_pm; > > + > > + ret = v4l2_async_register_subdev(&csi2->subdev); > > + if (ret < 0) > > + goto error_subdev; > > + > > + dev_info(csi2->dev, "%d lanes found\n", csi2->lanes); > > This is also printed if you enable debug level prints in v4l2 fwnode. > Agreed, I will drop it. > > + > > + return 0; > > + > > +error_subdev: > > + clk_disable_unprepare(csi2->pclk); > > + v4l2_subdev_cleanup(&csi2->subdev); > > +error_pm: > > + pm_runtime_disable(&pdev->dev); > > +error_async: > > + v4l2_async_nf_unregister(&csi2->notifier); > > + v4l2_async_nf_cleanup(&csi2->notifier); > > + > > + return ret; > > +} > > + > > +static const struct of_device_id rzg2l_csi2_of_table[] = { > > + { .compatible = "renesas,rzg2l-csi2", }, > > + { /* sentinel */ } > > +}; > > + > > +static int rzg2l_csi2_remove(struct platform_device *pdev) > > +{ > > + struct rzg2l_csi2 *csi2 = platform_get_drvdata(pdev); > > + > > + v4l2_async_nf_unregister(&csi2->notifier); > > + v4l2_async_nf_cleanup(&csi2->notifier); > > + v4l2_async_unregister_subdev(&csi2->subdev); > > + clk_disable_unprepare(csi2->pclk); > > + v4l2_subdev_cleanup(&csi2->subdev); > > + > > + pm_runtime_disable(&pdev->dev); > > + > > + return 0; > > +} > > + > > +static struct platform_driver rzg2l_csi2_pdrv = { > > + .remove = rzg2l_csi2_remove, > > + .probe = rzg2l_csi2_probe, > > + .driver = { > > + .name = "rzg2l-csi2", > > + .of_match_table = rzg2l_csi2_of_table, > > + }, > > +}; > > + > > +module_platform_driver(rzg2l_csi2_pdrv); > > + > > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > > +MODULE_DESCRIPTION("Renesas RZ/G2L MIPI CSI2 receiver driver"); > > +MODULE_LICENSE("GPL"); > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > new file mode 100644 > > index 000000000000..f85a1d44250f > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.h > > @@ -0,0 +1,46 @@ > > +/* SPDX-License-Identifier: GPL-2.0+ */ > > +/* > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + */ > > + > > +#ifndef __RZG2L_CSI2__ > > +#define __RZG2L_CSI2__ > > + > > +enum rzg2l_csi2_pads { > > + RZG2L_CSI2_SINK = 0, > > + RZG2L_CSI2_SOURCE_VC0, > > + RZG2L_CSI2_SOURCE_VC1, > > + RZG2L_CSI2_SOURCE_VC2, > > + RZG2L_CSI2_SOURCE_VC3, > > + NR_OF_RZG2L_CSI2_PAD, > > +}; > > + > > +struct rzg2l_csi2 { > > + struct device *dev; > > + void __iomem *base; > > + struct reset_control *rstc; > > + struct clk *sysclk; > > + struct clk *vclk; > > + struct clk *pclk; > > + > > + struct v4l2_subdev subdev; > > + struct media_pad pads[NR_OF_RZG2L_CSI2_PAD]; > > + > > + struct v4l2_async_notifier notifier; > > + struct v4l2_subdev *remote_source; > > + > > + unsigned short lanes; > > + > > + unsigned long hsfreq; > > +}; > > + > > +static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) > > +{ > > + return container_of(sd, struct rzg2l_csi2, subdev); > > +} > > + > > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2); > > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on); > > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on); > > Are these something that could be achieved using the standard interfaces, > as I believe the other drivers are doing? The pre_streamon and > post_streamon callbacks could be relevant for this. > Thanks for the pointer. I have now moved some code to pre_streamon () and rest to s_stream(). Is there any mandatory rule to have both implemented? (as I wont be needing post_streamoff(), nothing complains so for) Cheers, Prabhakar
Hi Prabhakar, On Thu, Sep 22, 2022 at 01:53:49PM +0100, Lad, Prabhakar wrote: > > > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2); > > > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on); > > > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on); > > > > Are these something that could be achieved using the standard interfaces, > > as I believe the other drivers are doing? The pre_streamon and > > post_streamon callbacks could be relevant for this. > > > Thanks for the pointer. I have now moved some code to pre_streamon () > and rest to s_stream(). Is there any mandatory rule to have both > implemented? (as I wont be needing post_streamoff(), nothing complains > so for) In principle no. But if you e.g. resume the device in pre_streamon, you'll need to suspend it in post_streamon.
Hi Sakari, On Thu, Sep 22, 2022 at 1:34 PM Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > > Hi Prabhakar, > > On Thu, Sep 22, 2022 at 01:08:33PM +0100, Lad, Prabhakar wrote: > > > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > > > the mipi/dhpy initialization happens as per the HW manual > > > > > > That doesn't look right. The driver doesn't use runtime PM anymore, so > > > power domains may not be handled properly. What was the problem with > > > clock handling using runtime PM ? > > > > > If we use the runtime PM all the clocks will be turned ON when we call > > pm_runtime_resume_and_get() which I dont want to. As per the "Starting > > reception for MIPI CSI-2 Input" section 35.3.1 for example we first > > need to turn ON all the clocks and later further down the line we need > > to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such > > cases I have switched to individual clock handling. > > If that is the case, then you should control just that clock directly, > outside runtime PM callbacks. > OK, I'll move the control outside runtime PM. > Runtime PM may be needed e.g. for resuming a parent device. > Agreed. Cheers, Prabhakar
Hi Sakari, On Thu, Sep 22, 2022 at 2:02 PM Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > > Hi Prabhakar, > > On Thu, Sep 22, 2022 at 01:53:49PM +0100, Lad, Prabhakar wrote: > > > > +int rzg2l_csi2_cmn_rstb_deassert(struct rzg2l_csi2 *csi2); > > > > +int rzg2l_csi2_dphy_setting(struct rzg2l_csi2 *csi2, bool on); > > > > +void rzg2l_csi2_mipi_link_setting(struct rzg2l_csi2 *csi2, bool on); > > > > > > Are these something that could be achieved using the standard interfaces, > > > as I believe the other drivers are doing? The pre_streamon and > > > post_streamon callbacks could be relevant for this. > > > > > Thanks for the pointer. I have now moved some code to pre_streamon () > > and rest to s_stream(). Is there any mandatory rule to have both > > implemented? (as I wont be needing post_streamoff(), nothing complains > > so for) > > In principle no. > OK. > But if you e.g. resume the device in pre_streamon, you'll need to suspend > it in post_streamon. > Agreed! Cheers, Prabhakar
Hi Geert, On Thu, Sep 22, 2022 at 1:51 PM Geert Uytterhoeven <geert@linux-m68k.org> wrote: > > On Thu, Sep 22, 2022 at 2:34 PM Sakari Ailus > <sakari.ailus@linux.intel.com> wrote: > > On Thu, Sep 22, 2022 at 01:08:33PM +0100, Lad, Prabhakar wrote: > > > > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > > > > the mipi/dhpy initialization happens as per the HW manual > > > > > > > > That doesn't look right. The driver doesn't use runtime PM anymore, so > > > > power domains may not be handled properly. What was the problem with > > > > clock handling using runtime PM ? > > > > > > > If we use the runtime PM all the clocks will be turned ON when we call > > > pm_runtime_resume_and_get() which I dont want to. As per the "Starting > > > reception for MIPI CSI-2 Input" section 35.3.1 for example we first > > > need to turn ON all the clocks and later further down the line we need > > > to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such > > > cases I have switched to individual clock handling. > > > > If that is the case, then you should control just that clock directly, > > outside runtime PM callbacks. > > > > Runtime PM may be needed e.g. for resuming a parent device. > > Exactly. > So probably you should not consider R9A07G044_CRU_VCLK a PM clock, > i.e. you need changes to rzg2l_cpg_is_pm_clk() to exclude it. > Thanks for the pointer. In that case we will have to consider R9A07G044_CRU_VCLK and R9A07G044_CRU_SYSCLK as not PM clocks. Does the below sound good? - DEF_NO_PM() macro - bool is_pm_clk in struct rzg2l_mod_clk. I still have to implement it, just wanted your opinion beforehand. Cheers, Prabhakar
On Thu, Sep 22, 2022 at 02:27:15PM +0100, Lad, Prabhakar wrote: > On Thu, Sep 22, 2022 at 1:51 PM Geert Uytterhoeven wrote: > > On Thu, Sep 22, 2022 at 2:34 PM Sakari Ailus wrote: > > > On Thu, Sep 22, 2022 at 01:08:33PM +0100, Lad, Prabhakar wrote: > > > > > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > > > > > the mipi/dhpy initialization happens as per the HW manual > > > > > > > > > > That doesn't look right. The driver doesn't use runtime PM anymore, so > > > > > power domains may not be handled properly. What was the problem with > > > > > clock handling using runtime PM ? > > > > > > > > > If we use the runtime PM all the clocks will be turned ON when we call > > > > pm_runtime_resume_and_get() which I dont want to. As per the "Starting > > > > reception for MIPI CSI-2 Input" section 35.3.1 for example we first > > > > need to turn ON all the clocks and later further down the line we need > > > > to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such > > > > cases I have switched to individual clock handling. > > > > > > If that is the case, then you should control just that clock directly, > > > outside runtime PM callbacks. > > > > > > Runtime PM may be needed e.g. for resuming a parent device. > > > > Exactly. > > So probably you should not consider R9A07G044_CRU_VCLK a PM clock, > > i.e. you need changes to rzg2l_cpg_is_pm_clk() to exclude it. > > Thanks for the pointer. In that case we will have to consider > R9A07G044_CRU_VCLK and R9A07G044_CRU_SYSCLK as not PM clocks. I like when patch review discussions continue on the list without my involvement, and the end result is exactly what I would have advised :-) > Does the below sound good? > - DEF_NO_PM() macro > - bool is_pm_clk in struct rzg2l_mod_clk. > > I still have to implement it, just wanted your opinion beforehand. Looks good to me, but I'm no expert in this area. I trust Geert's advice.
Hi Prabhakar, Thank you for the patch. On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > Based on a patch in the BSP by Hien Huynh > <hien.huynh.px@renesas.com> > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > --- > v1 -> v2 > * No change > > RFC v2 -> v1 > * Moved the driver to renesas folder > * Fixed review comments pointed by Jacopo > > RFC v1 -> RFC v2 > * Dropped group > * Dropped CSI subdev and implemented as new driver > * Dropped "mc_" from function names > * Moved the driver to renesas folder > --- > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ I'd merge those two files together, they both handle the video node. There's a comment below that recommends adding a subdev, that should then go to a separate file. > 6 files changed, 1669 insertions(+) > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > index 57c40bb499df..08ff0e96b3f5 100644 > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > To compile this driver as a module, choose M here: the > module will be called rzg2l-csi2. > + > +config VIDEO_RZG2L_CRU > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > + depends on ARCH_RENESAS || COMPILE_TEST > + depends on V4L_PLATFORM_DRIVERS > + depends on VIDEO_DEV && OF > + select MEDIA_CONTROLLER > + select V4L2_FWNODE > + select VIDEOBUF2_DMA_CONTIG > + select VIDEO_RZG2L_CSI2 Is this required, can't the CRU be used with a parallel sensor without the CSI-2 receiver ? > + select VIDEO_V4L2_SUBDEV_API > + help > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > + Unit (CRU) driver. > + > + To compile this driver as a module, choose M here: the > + module will be called rzg2l-cru. > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > index 91ea97a944e6..7628809e953f 100644 > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > @@ -1,3 +1,6 @@ > # SPDX-License-Identifier: GPL-2.0 > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > + > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > new file mode 100644 > index 000000000000..b5d4110b1913 > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > @@ -0,0 +1,395 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Driver for Renesas RZ/G2L CRU > + * > + * Copyright (C) 2022 Renesas Electronics Corp. > + * > + * Based on Renesas R-Car VIN > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > + * Copyright (C) 2008 Magnus Damm > + */ > + > +#include <linux/clk.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/of_graph.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > + > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-mc.h> > + > +#include "rzg2l-cru.h" > + > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) As this macro is only used to get the rzg2l_cru_dev pointer from the v4l2_async_notifier pointer, you can replace it with #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) I would also turn it into a static inline function for additional compile-time type safety. > + > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > + unsigned int notification) > +{ > + struct media_entity *entity; > + struct rzg2l_cru_dev *cru; > + struct media_pad *csi_pad; > + struct v4l2_subdev *sd; > + int ret; > + > + ret = v4l2_pipeline_link_notify(link, flags, notification); > + if (ret) > + return ret; > + > + /* Only care about link enablement for CRU nodes. */ > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > + return 0; > + > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > + /* > + * Don't allow link changes if any entity in the graph is > + * streaming, modifying the CHSEL register fields can disrupt > + * running streams. > + */ > + media_device_for_each_entity(entity, &cru->mdev) > + if (media_entity_is_streaming(entity)) > + return -EBUSY; > + > + mutex_lock(&cru->mdev_lock); > + > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > + if (csi_pad) { > + ret = -EMLINK; > + goto out; > + } > + > + sd = media_entity_to_v4l2_subdev(link->source->entity); > + if (cru->csi.subdev == sd) { > + cru->csi.channel = link->source->index - 1; > + cru->is_csi = true; > + } else { > + ret = -ENODEV; > + } > + > +out: > + mutex_unlock(&cru->mdev_lock); > + > + return ret; > +} > + > +static const struct media_device_ops rzg2l_cru_media_ops = { > + .link_notify = rzg2l_cru_csi2_link_notify, > +}; > + > +/* ----------------------------------------------------------------------------- > + * Group async notifier > + */ > + > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > +{ > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > + unsigned int i; > + int ret; > + > + ret = media_device_register(&cru->mdev); > + if (ret) > + return ret; I'd move the v4l2_device_register() call here, as it's the V4L2 counterpart of the media device, and handling them together would be best. > + > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > + if (ret) { > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > + return ret; > + } > + > + if (!video_is_registered(&cru->vdev)) { Can this happen ? > + ret = rzg2l_cru_v4l2_register(cru); > + if (ret) > + return ret; > + } > + > + /* Create all media device links between CRU and CSI-2's. */ > + /* > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > + * should be implemented by streams API which is under development > + * so for now just link it to VC0 > + */ The streams API won't require more links, so I'd drop the comment and the loop and create a single link. > + for (i = 1; i <= 1; i++) { > + struct media_entity *source, *sink; > + > + source = &cru->csi.subdev->entity; > + sink = &cru->vdev.entity; Hmmm... I'd recommend adding a subdev to model the image processing pipeline of the CRU, between the CSI-2 receiver and the video node. That will help when you'll add support for parallel sensors, and it will also be needed by the streams API to select which virtual channel to capture. > + > + ret = media_create_pad_link(source, i, sink, 0, 0); > + if (ret) { > + dev_err(cru->dev, "Error adding link from %s to %s\n", > + source->name, sink->name); > + break; > + } > + } > + > + return ret; > +} > + > +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > + > + rzg2l_cru_v4l2_unregister(cru); > + > + mutex_lock(&cru->mdev_lock); > + > + if (cru->csi.asd == asd) { > + cru->csi.subdev = NULL; > + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); > + } > + > + mutex_unlock(&cru->mdev_lock); > + > + media_device_unregister(&cru->mdev); > +} > + > +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *subdev, > + struct v4l2_async_subdev *asd) > +{ > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > + > + mutex_lock(&cru->mdev_lock); > + > + if (cru->csi.asd == asd) { > + cru->csi.subdev = subdev; > + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); > + } > + > + mutex_unlock(&cru->mdev_lock); > + > + return 0; > +} > + > +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { > + .bound = rzg2l_cru_group_notify_bound, > + .unbind = rzg2l_cru_group_notify_unbind, > + .complete = rzg2l_cru_group_notify_complete, > +}; > + > +static int rvin_mc_parse_of(struct rzg2l_cru_dev *cru, unsigned int id) The id parameter is always 0, I'd drop it. > +{ > + struct v4l2_fwnode_endpoint vep = { > + .bus_type = V4L2_MBUS_CSI2_DPHY, > + }; > + struct fwnode_handle *ep, *fwnode; > + struct v4l2_async_subdev *asd; > + int ret; > + > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, id, 0); > + if (!ep) > + return 0; > + > + fwnode = fwnode_graph_get_remote_endpoint(ep); > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > + fwnode_handle_put(ep); > + if (ret) { > + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); > + ret = -EINVAL; > + goto out; > + } > + > + if (!of_device_is_available(to_of_node(fwnode))) { > + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", > + to_of_node(fwnode)); > + ret = -ENOTCONN; > + goto out; > + } > + > + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, > + struct v4l2_async_subdev); > + if (IS_ERR(asd)) { > + ret = PTR_ERR(asd); > + goto out; > + } > + > + cru->csi.asd = asd; > + > + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", > + to_of_node(fwnode), vep.base.id); > +out: > + fwnode_handle_put(fwnode); > + > + return ret; > +} > + > +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) > +{ > + int ret; > + > + v4l2_async_nf_init(&cru->notifier); > + > + ret = rvin_mc_parse_of(cru, 0); > + if (ret) > + return ret; > + > + cru->notifier.ops = &rzg2l_cru_async_ops; > + > + if (list_empty(&cru->notifier.asd_list)) > + return 0; > + > + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); > + if (ret < 0) { > + dev_err(cru->dev, "Notifier registration failed\n"); > + v4l2_async_nf_cleanup(&cru->notifier); > + return ret; > + } > + > + return 0; > +} > + > +static int rzg2l_cru_csi2_init(struct rzg2l_cru_dev *cru) The naming is a bit weird, as this isn't related to CSI-2. I would name the function rzg2l_cru_media_init(). > +{ > + struct media_device *mdev = NULL; > + const struct of_device_id *match; > + int ret; > + > + cru->pad.flags = MEDIA_PAD_FL_SINK; > + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); > + if (ret) > + return ret; > + > + mutex_init(&cru->mdev_lock); > + mdev = &cru->mdev; > + mdev->dev = cru->dev; > + mdev->ops = &rzg2l_cru_media_ops; > + > + match = of_match_node(cru->dev->driver->of_match_table, > + cru->dev->of_node); > + > + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); > + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); > + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", > + dev_name(mdev->dev)); > + > + cru->v4l2_dev.mdev = &cru->mdev; > + > + media_device_init(mdev); > + > + ret = rzg2l_cru_mc_parse_of_graph(cru); > + if (ret) { > + mutex_lock(&cru->mdev_lock); > + cru->v4l2_dev.mdev = NULL; > + mutex_unlock(&cru->mdev_lock); > + } > + > + return 0; > +} > + > +static int rzg2l_cru_probe(struct platform_device *pdev) > +{ > + struct rzg2l_cru_dev *cru; > + int irq, ret; > + > + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); > + if (!cru) > + return -ENOMEM; > + > + cru->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(cru->base)) > + return PTR_ERR(cru->base); > + > + cru->presetn = devm_reset_control_get(&pdev->dev, "presetn"); > + if (IS_ERR(cru->presetn)) > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), > + "failed to get cpg presetn\n"); > + > + cru->aresetn = devm_reset_control_get(&pdev->dev, "aresetn"); > + if (IS_ERR(cru->aresetn)) > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), > + "failed to get cpg aresetn\n"); > + > + cru->vclk = devm_clk_get(&pdev->dev, "vclk"); > + if (IS_ERR(cru->vclk)) { > + dev_err(&pdev->dev, "Failed to get vclk"); > + return PTR_ERR(cru->vclk); You could use dev_err_probe() here too (as well as below). > + } > + > + cru->pclk = devm_clk_get(&pdev->dev, "pclk"); > + if (IS_ERR(cru->pclk)) { > + dev_err(&pdev->dev, "Failed to get pclk"); > + return PTR_ERR(cru->pclk); > + } > + > + cru->aclk = devm_clk_get(&pdev->dev, "aclk"); > + if (IS_ERR(cru->aclk)) { > + dev_err(&pdev->dev, "Failed to get aclk"); > + return PTR_ERR(cru->aclk); > + } > + > + cru->dev = &pdev->dev; > + cru->info = of_device_get_match_data(&pdev->dev); > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return irq; > + > + ret = rzg2l_cru_dma_register(cru, irq); > + if (ret) > + return ret; > + > + platform_set_drvdata(pdev, cru); > + > + ret = rzg2l_cru_csi2_init(cru); > + if (ret) > + goto error_dma_unregister; > + > + cru->num_buf = HW_BUFFER_DEFAULT; > + pm_suspend_ignore_children(&pdev->dev, true); > + pm_runtime_enable(&pdev->dev); > + > + return 0; > + > +error_dma_unregister: > + rzg2l_cru_dma_unregister(cru); > + > + return ret; > +} > + > +static const struct of_device_id rzg2l_cru_of_id_table[] = { > + { > + .compatible = "renesas,rzg2l-cru", > + }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); > + > +static int rzg2l_cru_remove(struct platform_device *pdev) > +{ > + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); > + > + pm_runtime_disable(&pdev->dev); > + > + rzg2l_cru_v4l2_unregister(cru); > + > + v4l2_async_nf_unregister(&cru->notifier); > + v4l2_async_nf_cleanup(&cru->notifier); > + > + media_device_cleanup(&cru->mdev); > + mutex_destroy(&cru->mdev_lock); > + cru->v4l2_dev.mdev = NULL; Is this needed ? > + > + rzg2l_cru_dma_unregister(cru); > + > + return 0; > +} > + > +static struct platform_driver rzg2l_cru_driver = { > + .driver = { > + .name = "rzg2l-cru", > + .of_match_table = rzg2l_cru_of_id_table, > + }, > + .probe = rzg2l_cru_probe, > + .remove = rzg2l_cru_remove, No PM ? > +}; > + > +module_platform_driver(rzg2l_cru_driver); > + > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > new file mode 100644 > index 000000000000..a834680a3200 > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > @@ -0,0 +1,152 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Driver for Renesas RZ/G2L CRU > + * > + * Copyright (C) 2022 Renesas Electronics Corp. > + * Extra blank line. > + */ > + > +#ifndef __RZG2L_CRU__ > +#define __RZG2L_CRU__ > + > +#include <linux/reset.h> > + > +#include <media/v4l2-async.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-dev.h> > +#include <media/v4l2-device.h> > +#include <media/videobuf2-v4l2.h> > + > +/* Number of HW buffers */ > +#define HW_BUFFER_MAX 8 > +#define HW_BUFFER_DEFAULT 3 Could you prefix macro names with CRU_ (or RZG2L_CRU_, up to you) ? These names are a bit generic and could lead to clashes. > + > +/* Address alignment mask for HW buffers */ > +#define HW_BUFFER_MASK 0x1ff > + > +/* Maximum number of CSI2 virtual channels */ > +#define CSI2_VCHANNEL 4 > + > +#define CRU_MAX_INPUT_WIDTH 2800 > +#define CRU_MAX_INPUT_HEIGHT 4095 > + > +/** > + * enum rzg2l_cru_dma_state - DMA states > + * @RZG2L_CRU_DMA_STOPPED: No operation in progress > + * @RZG2L_CRU_DMA_STARTING: Capture starting up > + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers > + * @RZG2L_CRU_DMA_STOPPING: Stopping operation > + */ > +enum rzg2l_cru_dma_state { > + RZG2L_CRU_DMA_STOPPED = 0, > + RZG2L_CRU_DMA_STARTING, > + RZG2L_CRU_DMA_RUNNING, > + RZG2L_CRU_DMA_STOPPING, > +}; > + > +struct rzg2l_cru_csi { > + struct v4l2_async_subdev *asd; > + struct v4l2_subdev *subdev; > + u32 channel; > +}; > + > +/** > + * struct rzg2l_cru_dev - Renesas CRU device structure > + * @dev: (OF) device > + * @base: device I/O register space remapped to virtual memory > + * @info: info about CRU instance > + * > + * @presetn: CRU_PRESETN reset line > + * @aresetn: CRU_ARESETN reset line > + * > + * @vclk: CRU Main clock > + * @pclk: CPU Register access clock > + * @aclk: CRU image transfer clock > + * > + * @vdev: V4L2 video device associated with CRU > + * @v4l2_dev: V4L2 device > + * @ctrl_handler: V4L2 control handler > + * @num_buf: Holds the current number of buffers enabled > + * @notifier: V4L2 asynchronous subdevs notifier > + * > + * @csi: CSI info > + * @mdev: media device > + * @mdev_lock: protects the count, notifier and csi members > + * @pad: media pad for the video device entity > + * > + * @lock: protects @queue > + * @queue: vb2 buffers queue > + * @scratch: cpu address for scratch buffer > + * @scratch_phys: physical address of the scratch buffer > + * > + * @qlock: protects @queue_buf, @buf_list, @sequence > + * @state > + * @queue_buf: Keeps track of buffers given to HW slot > + * @buf_list: list of queued buffers > + * @sequence: V4L2 buffers sequence number > + * @state: keeps track of operation state > + * > + * @is_csi: flag to mark the CRU as using a CSI-2 subdevice > + * > + * @input_is_yuv: flag to mark the input format of CRU > + * @output_is_yuv: flag to mark the output format of CRU > + * > + * @mbus_code: media bus format code > + * @format: active V4L2 pixel format > + * > + * @compose: active composing > + */ > +struct rzg2l_cru_dev { > + struct device *dev; > + void __iomem *base; > + const struct rzg2l_cru_info *info; > + > + struct reset_control *presetn; > + struct reset_control *aresetn; > + > + struct clk *vclk; > + struct clk *pclk; > + struct clk *aclk; > + > + struct video_device vdev; > + struct v4l2_device v4l2_dev; > + u8 num_buf; > + > + struct v4l2_async_notifier notifier; > + > + struct rzg2l_cru_csi csi; > + struct media_device mdev; > + struct mutex mdev_lock; > + struct media_pad pad; > + > + struct mutex lock; > + struct vb2_queue queue; > + void *scratch; > + dma_addr_t scratch_phys; > + > + spinlock_t qlock; > + struct vb2_v4l2_buffer *queue_buf[HW_BUFFER_MAX]; > + struct list_head buf_list; > + unsigned int sequence; > + enum rzg2l_cru_dma_state state; > + > + bool is_csi; > + > + bool input_is_yuv; > + bool output_is_yuv; > + > + u32 mbus_code; > + struct v4l2_pix_format format; > + > + struct v4l2_rect compose; > +}; > + > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq); > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); > + > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru); > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru); > + > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); > + > +#endif > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > new file mode 100644 > index 000000000000..44efd071f562 > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > @@ -0,0 +1,734 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Driver for Renesas RZ/G2L CRU > + * > + * Copyright (C) 2022 Renesas Electronics Corp. > + * > + * Based on Renesas R-Car VIN > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > + * Copyright (C) 2008 Magnus Damm > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/interrupt.h> > + > +#include <media/videobuf2-dma-contig.h> > + > +#include "rzg2l-cru.h" > +#include "rzg2l-csi2.h" > + > +/* HW CRU Registers Definition */ > +/* CRU Control Register */ > +#define CRUnCTRL 0x0 > +#define CRUnCTRL_VINSEL(x) ((x) << 0) > + > +/* CRU Interrupt Enable Register */ > +#define CRUnIE 0x4 > +#define CRUnIE_SFE BIT(16) > +#define CRUnIE_EFE BIT(17) > + > +/* CRU Interrupt Status Register */ > +#define CRUnINTS 0x8 > +#define CRUnINTS_SFS BIT(16) > + > +/* CRU Reset Register */ > +#define CRUnRST 0xc > +#define CRUnRST_VRESETN BIT(0) > + > +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ > +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) > + > +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ > +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) > + > +/* Memory Bank Enable Register for CRU Image Data */ > +#define AMnMBVALID 0x148 > +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) > + > +/* Memory Bank Status Register for CRU Image Data */ > +#define AMnMBS 0x14c > +#define AMnMBS_MBSTS 0x7 > + > +/* AXI Master FIFO Pointer Register for CRU Image Data */ > +#define AMnFIFOPNTR 0x168 > +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) > +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) > + > +/* AXI Master Transfer Stop Register for CRU Image Data */ > +#define AMnAXISTP 0x174 > +#define AMnAXISTP_AXI_STOP BIT(0) > + > +/* AXI Master Transfer Stop Status Register for CRU Image Data */ > +#define AMnAXISTPACK 0x178 > +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) > + > +/* CRU Image Processing Enable Register */ > +#define ICnEN 0x200 > +#define ICnEN_ICEN BIT(0) > + > +/* CRU Image Processing Main Control Register */ > +#define ICnMC 0x208 > +#define ICnMC_CSCTHR BIT(5) > +#define ICnMC_INF_YUV8_422 (0x1e << 16) > +#define ICnMC_INF_USER (0x30 << 16) > +#define ICnMC_VCSEL(x) ((x) << 22) > +#define ICnMC_INF_MASK GENMASK(21, 16) > + > +/* CRU Module Status Register */ > +#define ICnMS 0x254 > +#define ICnMS_IA BIT(2) > + > +/* CRU Data Output Mode Register */ > +#define ICnDMR 0x26c > +#define ICnDMR_YCMODE_UYVY (1 << 4) > + > +#define RZG2L_TIMEOUT_MS 100 > +#define RZG2L_RETRIES 10 > + > +struct rzg2l_cru_buffer { > + struct vb2_v4l2_buffer vb; > + struct list_head list; > +}; > + > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > + struct rzg2l_cru_buffer, \ > + vb)->list) > + > +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) > +{ > + iowrite32(value, cru->base + offset); > +} > + > +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) > +{ > + return ioread32(cru->base + offset); > +} > + > +/* Need to hold qlock before calling */ > +static void return_unused_buffers(struct rzg2l_cru_dev *cru, > + enum vb2_buffer_state state) > +{ > + struct rzg2l_cru_buffer *buf, *node; > + unsigned long flags; > + unsigned int i; > + > + spin_lock_irqsave(&cru->qlock, flags); > + for (i = 0; i < cru->num_buf; i++) { > + if (cru->queue_buf[i]) { > + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, > + state); > + cru->queue_buf[i] = NULL; > + } > + } > + > + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { > + vb2_buffer_done(&buf->vb.vb2_buf, state); > + list_del(&buf->list); > + } > + spin_unlock_irqrestore(&cru->qlock, flags); > +} > + > +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, > + unsigned int *nplanes, unsigned int sizes[], > + struct device *alloc_devs[]) > +{ > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > + > + /* Make sure the image size is large enough. */ > + if (*nplanes) > + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; > + > + *nplanes = 1; > + sizes[0] = cru->format.sizeimage; > + > + return 0; > +}; > + > +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) > +{ > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > + unsigned long size = cru->format.sizeimage; > + > + if (vb2_plane_size(vb, 0) < size) { > + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", > + vb2_plane_size(vb, 0), size); > + return -EINVAL; > + } > + > + vb2_set_plane_payload(vb, 0, size); > + > + return 0; > +} > + > +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) > +{ > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > + unsigned long flags; > + > + spin_lock_irqsave(&cru->qlock, flags); > + > + list_add_tail(to_buf_list(vbuf), &cru->buf_list); > + > + spin_unlock_irqrestore(&cru->qlock, flags); > +} > + > +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, > + struct v4l2_subdev *sd, > + struct media_pad *pad) > +{ > + struct v4l2_subdev_format fmt = { > + .which = V4L2_SUBDEV_FORMAT_ACTIVE, > + }; > + > + fmt.pad = pad->index; > + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) > + return -EPIPE; > + > + if (cru->is_csi) { > + switch (fmt.format.code) { > + case MEDIA_BUS_FMT_UYVY8_1X16: > + break; > + default: > + return -EPIPE; > + } > + } > + cru->mbus_code = fmt.format.code; > + > + switch (fmt.format.field) { > + case V4L2_FIELD_TOP: > + case V4L2_FIELD_BOTTOM: > + case V4L2_FIELD_NONE: > + case V4L2_FIELD_INTERLACED_TB: > + case V4L2_FIELD_INTERLACED_BT: > + case V4L2_FIELD_INTERLACED: > + case V4L2_FIELD_SEQ_TB: > + case V4L2_FIELD_SEQ_BT: > + break; > + default: > + return -EPIPE; > + } > + > + if (fmt.format.width != cru->format.width || > + fmt.format.height != cru->format.height || > + fmt.format.code != cru->mbus_code) > + return -EPIPE; > + > + return 0; > +} > + > +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, > + int slot, dma_addr_t addr) > +{ > + const struct v4l2_format_info *fmt; > + int offsetx, offsety; > + dma_addr_t offset; > + > + fmt = rzg2l_cru_format_from_pixel(cru->format.pixelformat); > + > + /* > + * There is no HW support for composition do the best we can > + * by modifying the buffer offset > + */ > + offsetx = cru->compose.left * fmt->bpp[0]; > + offsety = cru->compose.top * cru->format.bytesperline; > + offset = addr + offsetx + offsety; > + > + /* > + * The address needs to be 512 bytes aligned. Driver should never accept > + * settings that do not satisfy this in the first place... > + */ > + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) > + return; > + > + /* Currently, we just use the buffer in 32 bits address */ > + rzg2l_cru_write(cru, AMnMBxADDRL(slot), offset); > + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); > +} > + > +/* > + * Moves a buffer from the queue to the HW slot. If no buffer is > + * available use the scratch buffer. The scratch buffer is never > + * returned to userspace, its only function is to enable the capture > + * loop to keep running. > + */ > +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) > +{ > + struct vb2_v4l2_buffer *vbuf; > + struct rzg2l_cru_buffer *buf; > + dma_addr_t phys_addr; > + > + /* A already populated slot shall never be overwritten. */ > + if (WARN_ON(cru->queue_buf[slot])) > + return; > + > + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); > + > + if (list_empty(&cru->buf_list)) { > + cru->queue_buf[slot] = NULL; > + phys_addr = cru->scratch_phys; > + } else { > + /* Keep track of buffer we give to HW */ > + buf = list_entry(cru->buf_list.next, > + struct rzg2l_cru_buffer, list); > + vbuf = &buf->vb; > + list_del_init(to_buf_list(vbuf)); > + cru->queue_buf[slot] = vbuf; > + > + /* Setup DMA */ > + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); > + } > + > + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); > +} > + > +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) > +{ > + unsigned int slot; > + > + /* > + * Set image data memory banks. > + * Currently, we will use maximum address. > + */ > + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); > + > + for (slot = 0; slot < cru->num_buf; slot++) > + rzg2l_cru_fill_hw_slot(cru, slot); > +} > + > +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru) > +{ > + u32 icnmc; > + > + switch (cru->mbus_code) { > + case MEDIA_BUS_FMT_UYVY8_1X16: > + icnmc = ICnMC_INF_YUV8_422; > + cru->input_is_yuv = true; > + break; > + default: > + cru->input_is_yuv = false; > + icnmc = ICnMC_INF_USER; > + break; > + } > + > + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); > + > + /* Set virtual channel CSI2 */ > + icnmc |= ICnMC_VCSEL(cru->csi.channel); > + > + rzg2l_cru_write(cru, ICnMC, icnmc); > +} > + > +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru) > +{ > + u32 icndmr; > + > + if (cru->is_csi) > + rzg2l_cru_csi2_setup(cru); > + > + /* Output format */ > + switch (cru->format.pixelformat) { > + case V4L2_PIX_FMT_UYVY: > + icndmr = ICnDMR_YCMODE_UYVY; > + cru->output_is_yuv = true; > + break; > + default: > + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", > + cru->format.pixelformat); > + return -EINVAL; > + } > + > + /* If input and output use same colorspace, do bypass mode */ > + if (cru->output_is_yuv == cru->input_is_yuv) > + rzg2l_cru_write(cru, ICnMC, > + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); > + else > + rzg2l_cru_write(cru, ICnMC, > + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); > + > + /* Set output data format */ > + rzg2l_cru_write(cru, ICnDMR, icndmr); > + > + return 0; > +} > + > +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) > +{ > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > + struct media_pipeline *pipe; > + struct v4l2_subdev *sd; > + struct media_pad *pad; > + unsigned long flags; > + int ret; > + > + pad = media_pad_remote_pad_first(&cru->pad); > + if (!pad) > + return -EPIPE; > + > + sd = media_entity_to_v4l2_subdev(pad->entity); > + > + if (!on) { > + media_pipeline_stop(&cru->vdev.entity); > + return v4l2_subdev_call(sd, video, s_stream, 0); > + } > + > + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); > + if (ret) > + return ret; > + > + rzg2l_csi2_dphy_setting(csi2, 1); > + > + spin_lock_irqsave(&cru->qlock, flags); > + > + /* Select a video input */ > + if (cru->is_csi) > + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); > + > + /* Cancel the software reset for image processing block */ > + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); > + > + /* Disable and clear the interrupt before using */ > + rzg2l_cru_write(cru, CRUnIE, 0); > + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); > + > + /* Initialize the AXI master */ > + rzg2l_cru_initialize_axi(cru); > + > + /* Initialize image convert */ > + ret = rzg2l_cru_initialize_image_conv(cru); > + if (ret) { > + spin_unlock_irqrestore(&cru->qlock, flags); > + return ret; > + } > + > + /* Enable interrupt */ > + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); > + > + /* Enable image processing reception */ > + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); > + > + spin_unlock_irqrestore(&cru->qlock, flags); > + > + pipe = sd->entity.pipe ? sd->entity.pipe : &cru->vdev.pipe; > + ret = media_pipeline_start(&cru->vdev.entity, pipe); > + if (ret) > + return ret; > + > + clk_disable_unprepare(cru->vclk); > + > + rzg2l_csi2_mipi_link_setting(csi2, 1); > + > + ret = clk_prepare_enable(cru->vclk); > + if (ret) > + return ret; > + > + rzg2l_csi2_cmn_rstb_deassert(csi2); > + > + ret = v4l2_subdev_call(sd, video, s_stream, 1); > + if (ret == -ENOIOCTLCMD) > + ret = 0; > + if (ret) > + media_pipeline_stop(&cru->vdev.entity); > + > + return ret; > +} > + > +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) > +{ > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; > + unsigned int retries = 0; > + unsigned long flags; > + u32 icnms; > + > + cru->state = RZG2L_CRU_DMA_STOPPING; > + > + rzg2l_cru_set_stream(cru, 0); > + > + rzg2l_csi2_dphy_setting(csi2, 0); > + > + rzg2l_csi2_mipi_link_setting(csi2, 0); > + > + spin_lock_irqsave(&cru->qlock, flags); > + > + /* Disable and clear the interrupt */ > + rzg2l_cru_write(cru, CRUnIE, 0); > + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); > + > + /* Stop the operation of image conversion */ > + rzg2l_cru_write(cru, ICnEN, 0); > + > + /* Wait for streaming to stop */ > + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { > + spin_unlock_irqrestore(&cru->qlock, flags); > + msleep(RZG2L_TIMEOUT_MS); > + spin_lock_irqsave(&cru->qlock, flags); > + } > + > + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; > + if (icnms) > + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); > + > + cru->state = RZG2L_CRU_DMA_STOPPED; > + > + /* Wait until the FIFO becomes empty */ > + for (retries = 5; retries > 0; retries--) { > + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); > + > + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; > + amnfifopntr_r_y = > + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; > + if (amnfifopntr_w == amnfifopntr_r_y) > + break; > + > + usleep_range(10, 20); > + } > + > + /* Notify that FIFO is not empty here */ > + if (!retries) > + dev_err(cru->dev, "Failed to empty FIFO\n"); > + > + /* Stop AXI bus */ > + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); > + > + /* Wait until the AXI bus stop */ > + for (retries = 5; retries > 0; retries--) { > + if (rzg2l_cru_read(cru, AMnAXISTPACK) & > + AMnAXISTPACK_AXI_STOP_ACK) > + break; > + > + usleep_range(10, 20); > + }; > + > + /* Notify that AXI bus can not stop here */ > + if (!retries) > + dev_err(cru->dev, "Failed to stop AXI bus\n"); > + > + /* Cancel the AXI bus stop request */ > + rzg2l_cru_write(cru, AMnAXISTP, 0); > + > + /* Resets the image processing module */ > + rzg2l_cru_write(cru, CRUnRST, 0); > + > + spin_unlock_irqrestore(&cru->qlock, flags); > + > + /* Set reset state */ > + reset_control_assert(cru->aresetn); > +} > + > +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) > +{ > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > + int ret; > + > + /* Release reset state */ > + ret = reset_control_deassert(cru->aresetn); > + if (ret) { > + dev_err(cru->dev, "failed to deassert aresetn\n"); > + return ret; > + } > + > + /* Allocate scratch buffer. */ > + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, > + &cru->scratch_phys, GFP_KERNEL); > + if (!cru->scratch) { > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); > + return -ENOMEM; > + } > + > + cru->sequence = 0; > + > + ret = rzg2l_cru_set_stream(cru, 1); > + if (ret) { > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > + goto out; > + } > + > + cru->state = RZG2L_CRU_DMA_STARTING; > + > + dev_dbg(cru->dev, "Starting to capture\n"); > + > +out: > + if (ret) > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > + cru->scratch_phys); > + > + return ret; > +} > + > +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) > +{ > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > + > + rzg2l_cru_stop_streaming(cru); > + > + /* Free scratch buffer */ > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > + cru->scratch_phys); > + > + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); > +} > + > +static const struct vb2_ops rzg2l_cru_qops = { > + .queue_setup = rzg2l_cru_queue_setup, > + .buf_prepare = rzg2l_cru_buffer_prepare, > + .buf_queue = rzg2l_cru_buffer_queue, > + .start_streaming = rzg2l_cru_start_streaming_vq, > + .stop_streaming = rzg2l_cru_stop_streaming_vq, > + .wait_prepare = vb2_ops_wait_prepare, > + .wait_finish = vb2_ops_wait_finish, > +}; > + > +static irqreturn_t rzg2l_cru_irq(int irq, void *data) > +{ > + struct rzg2l_cru_dev *cru = data; > + unsigned int handled = 0; > + unsigned long flags; > + u32 irq_status; > + u32 amnmbs; > + int slot; > + > + spin_lock_irqsave(&cru->qlock, flags); > + > + irq_status = rzg2l_cru_read(cru, CRUnINTS); > + if (!irq_status) > + goto done; > + > + handled = 1; > + > + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); > + > + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ > + if (cru->state == RZG2L_CRU_DMA_STOPPED) { > + dev_dbg(cru->dev, "IRQ while state stopped\n"); > + goto done; > + } > + > + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ > + if (cru->state == RZG2L_CRU_DMA_STOPPING) { > + if (irq_status & CRUnINTS_SFS) > + dev_dbg(cru->dev, "IRQ while state stopping\n"); > + goto done; > + } > + > + /* Prepare for capture and update state */ > + amnmbs = rzg2l_cru_read(cru, AMnMBS); > + slot = amnmbs & AMnMBS_MBSTS; > + > + /* > + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). > + * Recalculate to get the current transfer complete MB. > + */ > + if (slot == 0) > + slot = cru->num_buf - 1; > + else > + slot--; > + > + /* > + * To hand buffers back in a known order to userspace start > + * to capture first from slot 0. > + */ > + if (cru->state == RZG2L_CRU_DMA_STARTING) { > + if (slot != 0) { > + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); > + goto done; > + } > + > + dev_dbg(cru->dev, "Capture start synced!\n"); > + cru->state = RZG2L_CRU_DMA_RUNNING; > + } > + > + /* Capture frame */ > + if (cru->queue_buf[slot]) { > + cru->queue_buf[slot]->field = cru->format.field; > + cru->queue_buf[slot]->sequence = cru->sequence; > + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); > + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, > + VB2_BUF_STATE_DONE); > + cru->queue_buf[slot] = NULL; > + } else { > + /* Scratch buffer was used, dropping frame. */ > + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); > + } > + > + cru->sequence++; > + > + /* Prepare for next frame */ > + rzg2l_cru_fill_hw_slot(cru, slot); > + > +done: > + spin_unlock_irqrestore(&cru->qlock, flags); > + > + return IRQ_RETVAL(handled); > +} > + > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) > +{ > + mutex_destroy(&cru->lock); > + > + v4l2_device_unregister(&cru->v4l2_dev); > + reset_control_assert(cru->presetn); > +} > + > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq) > +{ > + struct vb2_queue *q = &cru->queue; > + unsigned int i; > + int ret; > + > + ret = reset_control_deassert(cru->presetn); > + if (ret) { > + dev_err(cru->dev, "failed to deassert presetn\n"); > + return ret; > + } Shouldn't this be done when starting streaming instead ? > + > + /* Initialize the top-level structure */ > + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); > + if (ret) > + return ret; > + > + mutex_init(&cru->lock); > + INIT_LIST_HEAD(&cru->buf_list); > + > + spin_lock_init(&cru->qlock); > + > + cru->state = RZG2L_CRU_DMA_STOPPED; > + > + for (i = 0; i < HW_BUFFER_MAX; i++) > + cru->queue_buf[i] = NULL; > + > + /* buffer queue */ > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; No VB2_READ please, that's very inefficient. > + q->lock = &cru->lock; > + q->drv_priv = cru; > + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); > + q->ops = &rzg2l_cru_qops; > + q->mem_ops = &vb2_dma_contig_memops; > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > + q->min_buffers_needed = 4; Does the hardware really require 4 buffers to operate ? > + q->dev = cru->dev; > + > + ret = vb2_queue_init(q); > + if (ret < 0) { > + dev_err(cru->dev, "failed to initialize VB2 queue\n"); > + goto error; > + } > + > + /* IRQ */ > + ret = devm_request_irq(cru->dev, irq, rzg2l_cru_irq, IRQF_SHARED, > + KBUILD_MODNAME, cru); > + if (ret) { > + dev_err(cru->dev, "failed to request irq\n"); > + goto error; > + } > + > + return 0; > + > +error: > + rzg2l_cru_dma_unregister(cru); > + return ret; > +} > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > new file mode 100644 > index 000000000000..c565597f5769 > --- /dev/null > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > @@ -0,0 +1,368 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Driver for Renesas RZ/G2L CRU > + * > + * Copyright (C) 2022 Renesas Electronics Corp. > + * > + * Based on Renesas R-Car VIN > + * Copyright (C) 2016 Renesas Electronics Corp. > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > + * Copyright (C) 2008 Magnus Damm > + */ > + > +#include <linux/clk.h> > + > +#include <media/v4l2-event.h> > +#include <media/v4l2-ioctl.h> > +#include <media/v4l2-mc.h> > +#include <media/v4l2-rect.h> > + > +#include "rzg2l-cru.h" > + > +#define RZG2L_CRU_DEFAULT_FORMAT V4L2_PIX_FMT_UYVY > +#define RZG2L_CRU_DEFAULT_WIDTH 800 > +#define RZG2L_CRU_DEFAULT_HEIGHT 600 > +#define RZG2L_CRU_DEFAULT_FIELD V4L2_FIELD_NONE > +#define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB > + > +/* ----------------------------------------------------------------------------- > + * Format Conversions > + */ > + > +static const struct v4l2_format_info rzg2l_cru_formats[] = { > + { > + .format = V4L2_PIX_FMT_UYVY, > + .bpp[0] = 2, > + }, > +}; > + > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_formats); i++) > + if (rzg2l_cru_formats[i].format == format) > + return rzg2l_cru_formats + i; > + > + return NULL; > +} > + > +static u32 rzg2l_cru_format_bytesperline(struct v4l2_pix_format *pix) > +{ > + const struct v4l2_format_info *fmt; > + > + fmt = rzg2l_cru_format_from_pixel(pix->pixelformat); > + > + if (WARN_ON(!fmt)) > + return -EINVAL; > + > + return pix->width * fmt->bpp[0]; > +} > + > +static u32 rzg2l_cru_format_sizeimage(struct v4l2_pix_format *pix) > +{ > + return pix->bytesperline * pix->height; > +} > + > +static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, > + struct v4l2_pix_format *pix) > +{ > + if (!rzg2l_cru_format_from_pixel(pix->pixelformat)) > + pix->pixelformat = RZG2L_CRU_DEFAULT_FORMAT; > + > + switch (pix->field) { > + case V4L2_FIELD_TOP: > + case V4L2_FIELD_BOTTOM: > + case V4L2_FIELD_NONE: > + case V4L2_FIELD_INTERLACED_TB: > + case V4L2_FIELD_INTERLACED_BT: > + case V4L2_FIELD_INTERLACED: > + break; > + default: > + pix->field = RZG2L_CRU_DEFAULT_FIELD; > + break; > + } > + > + /* Limit to CRU capabilities */ > + v4l_bound_align_image(&pix->width, 320, CRU_MAX_INPUT_WIDTH, 1, > + &pix->height, 240, CRU_MAX_INPUT_HEIGHT, 2, 0); > + > + pix->bytesperline = rzg2l_cru_format_bytesperline(pix); > + pix->sizeimage = rzg2l_cru_format_sizeimage(pix); > + > + dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", > + pix->width, pix->height, pix->bytesperline, pix->sizeimage); > +} > + > +static void rzg2l_cru_try_format(struct rzg2l_cru_dev *cru, > + struct v4l2_pix_format *pix) > +{ > + /* > + * The V4L2 specification clearly documents the colorspace fields > + * as being set by drivers for capture devices. Using the values > + * supplied by userspace thus wouldn't comply with the API. Until > + * the API is updated force fixed values. > + */ > + pix->colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; > + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); > + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); > + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, > + pix->ycbcr_enc); > + > + rzg2l_cru_format_align(cru, pix); > +} > + > +static int rzg2l_cru_querycap(struct file *file, void *priv, > + struct v4l2_capability *cap) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + > + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); > + strscpy(cap->card, "RZG2L_CRU", sizeof(cap->card)); > + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", > + dev_name(cru->dev)); > + return 0; > +} > + > +static int rzg2l_cru_try_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_format *f) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + > + rzg2l_cru_try_format(cru, &f->fmt.pix); > + > + return 0; > +} > + > +static int rzg2l_cru_s_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_format *f) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + > + if (vb2_is_busy(&cru->queue)) > + return -EBUSY; > + > + rzg2l_cru_try_format(cru, &f->fmt.pix); > + > + cru->format = f->fmt.pix; > + > + cru->compose.top = 0; > + cru->compose.left = 0; > + cru->compose.width = cru->format.width; > + cru->compose.height = cru->format.height; > + > + return 0; > +} > + > +static int rzg2l_cru_g_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_format *f) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + > + f->fmt.pix = cru->format; > + > + return 0; > +} > + > +static int rzg2l_cru_enum_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_fmtdesc *f) > +{ > + if (f->index >= ARRAY_SIZE(rzg2l_cru_formats)) > + return -EINVAL; > + > + f->pixelformat = rzg2l_cru_formats[f->index].format; > + > + return 0; > +} > + > +static int rzg2l_cru_subscribe_event(struct v4l2_fh *fh, > + const struct v4l2_event_subscription *sub) > +{ > + switch (sub->type) { > + case V4L2_EVENT_SOURCE_CHANGE: > + return v4l2_event_subscribe(fh, sub, 4, NULL); > + } > + return v4l2_ctrl_subscribe_event(fh, sub); > +} > + > +static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { > + .vidioc_querycap = rzg2l_cru_querycap, > + .vidioc_try_fmt_vid_cap = rzg2l_cru_try_fmt_vid_cap, > + .vidioc_g_fmt_vid_cap = rzg2l_cru_g_fmt_vid_cap, > + .vidioc_s_fmt_vid_cap = rzg2l_cru_s_fmt_vid_cap, > + .vidioc_enum_fmt_vid_cap = rzg2l_cru_enum_fmt_vid_cap, > + > + .vidioc_reqbufs = vb2_ioctl_reqbufs, > + .vidioc_create_bufs = vb2_ioctl_create_bufs, > + .vidioc_querybuf = vb2_ioctl_querybuf, > + .vidioc_qbuf = vb2_ioctl_qbuf, > + .vidioc_dqbuf = vb2_ioctl_dqbuf, > + .vidioc_expbuf = vb2_ioctl_expbuf, > + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, > + .vidioc_streamon = vb2_ioctl_streamon, > + .vidioc_streamoff = vb2_ioctl_streamoff, > + > + .vidioc_log_status = v4l2_ctrl_log_status, > + .vidioc_subscribe_event = rzg2l_cru_subscribe_event, > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, > +}; > + > +/* ----------------------------------------------------------------------------- > + * Media controller file operations > + */ > + > +static int rzg2l_cru_open(struct file *file) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + int ret; > + > + ret = clk_prepare_enable(cru->pclk); > + if (ret) > + return ret; > + > + ret = clk_prepare_enable(cru->vclk); > + if (ret) > + goto disable_pclk; > + > + ret = clk_prepare_enable(cru->aclk); > + if (ret) > + goto disable_vclk; > + > + ret = mutex_lock_interruptible(&cru->lock); > + if (ret) > + goto disable_aclk; > + > + file->private_data = cru; > + ret = v4l2_fh_open(file); > + if (ret) > + goto err_unlock; > + > + ret = v4l2_pipeline_pm_get(&cru->vdev.entity); > + if (ret < 0) > + goto err_open; > + > + mutex_unlock(&cru->lock); > + > + return 0; > +err_open: > + v4l2_fh_release(file); > +err_unlock: > + mutex_unlock(&cru->lock); > +disable_aclk: > + clk_disable_unprepare(cru->aclk); > +disable_vclk: > + clk_disable_unprepare(cru->vclk); > +disable_pclk: > + clk_disable_unprepare(cru->pclk); > + > + return ret; > +} > + > +static int rzg2l_cru_release(struct file *file) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + int ret; > + > + mutex_lock(&cru->lock); > + > + /* the release helper will cleanup any on-going streaming. */ > + ret = _vb2_fop_release(file, NULL); > + > + v4l2_pipeline_pm_put(&cru->vdev.entity); > + clk_disable_unprepare(cru->aclk); > + clk_disable_unprepare(cru->vclk); > + clk_disable_unprepare(cru->pclk); > + > + mutex_unlock(&cru->lock); > + > + return ret; > +} > + > +static const struct v4l2_file_operations rzg2l_cru_fops = { > + .owner = THIS_MODULE, > + .unlocked_ioctl = video_ioctl2, > + .open = rzg2l_cru_open, > + .release = rzg2l_cru_release, > + .poll = vb2_fop_poll, > + .mmap = vb2_fop_mmap, > + .read = vb2_fop_read, > +}; > + > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru) > +{ > + if (!video_is_registered(&cru->vdev)) > + return; > + > + v4l2_info(&cru->v4l2_dev, "Removed %s\n", > + video_device_node_name(&cru->vdev)); You can use dev_info(), but I'd actually use dev_dbg(). Same below. > + > + /* Checks internally if vdev have been init or not */ > + video_unregister_device(&cru->vdev); > +} > + > +static void rzg2l_cru_notify(struct v4l2_subdev *sd, > + unsigned int notification, void *arg) > +{ > + struct rzg2l_cru_dev *cru = > + container_of(sd->v4l2_dev, struct rzg2l_cru_dev, v4l2_dev); > + struct v4l2_subdev *remote; > + struct media_pad *pad; > + > + pad = media_pad_remote_pad_first(&cru->pad); > + if (!pad) > + return; > + > + remote = media_entity_to_v4l2_subdev(pad->entity); > + if (remote != sd) > + return; > + > + switch (notification) { > + case V4L2_DEVICE_NOTIFY_EVENT: > + v4l2_event_queue(&cru->vdev, arg); > + break; > + } > +} Drop this, userspace should listen for events on the subdevices that generate them. > + > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru) > +{ > + struct video_device *vdev = &cru->vdev; > + int ret; > + > + cru->v4l2_dev.notify = rzg2l_cru_notify; > + > + /* video node */ > + vdev->v4l2_dev = &cru->v4l2_dev; > + vdev->queue = &cru->queue; > + snprintf(vdev->name, sizeof(vdev->name), "CRU output"); > + vdev->release = video_device_release_empty; > + vdev->lock = &cru->lock; > + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | > + V4L2_CAP_READWRITE; No read/write please. > + > + /* Set a default format */ > + cru->format.pixelformat = RZG2L_CRU_DEFAULT_FORMAT; > + cru->format.width = RZG2L_CRU_DEFAULT_WIDTH; > + cru->format.height = RZG2L_CRU_DEFAULT_HEIGHT; > + cru->format.field = RZG2L_CRU_DEFAULT_FIELD; > + cru->format.colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; > + > + vdev->device_caps |= V4L2_CAP_IO_MC; > + vdev->fops = &rzg2l_cru_fops; > + vdev->ioctl_ops = &rzg2l_cru_ioctl_ops; > + > + rzg2l_cru_format_align(cru, &cru->format); I'd perform all this initialization in an init function called at probe time, with only the actual registration left here, to be done at bound time. > + > + ret = video_register_device(&cru->vdev, VFL_TYPE_VIDEO, -1); > + if (ret) { > + dev_err(cru->dev, "Failed to register video device\n"); > + return ret; > + } > + > + video_set_drvdata(&cru->vdev, cru); > + > + v4l2_info(&cru->v4l2_dev, "Device registered as %s\n", > + video_device_node_name(&cru->vdev)); > + > + return ret; > +}
Hi Prabhakar, On Thu, Sep 22, 2022 at 3:27 PM Lad, Prabhakar <prabhakar.csengg@gmail.com> wrote: > On Thu, Sep 22, 2022 at 1:51 PM Geert Uytterhoeven <geert@linux-m68k.org> wrote: > > On Thu, Sep 22, 2022 at 2:34 PM Sakari Ailus > > <sakari.ailus@linux.intel.com> wrote: > > > On Thu, Sep 22, 2022 at 01:08:33PM +0100, Lad, Prabhakar wrote: > > > > > > * Switched to manually turn ON/OFF the clocks instead of pm_runtime so that > > > > > > the mipi/dhpy initialization happens as per the HW manual > > > > > > > > > > That doesn't look right. The driver doesn't use runtime PM anymore, so > > > > > power domains may not be handled properly. What was the problem with > > > > > clock handling using runtime PM ? > > > > > > > > > If we use the runtime PM all the clocks will be turned ON when we call > > > > pm_runtime_resume_and_get() which I dont want to. As per the "Starting > > > > reception for MIPI CSI-2 Input" section 35.3.1 for example we first > > > > need to turn ON all the clocks and later further down the line we need > > > > to just turn OFF VCLK -> Enable Link -> turn ON VCLK. Due to such > > > > cases I have switched to individual clock handling. > > > > > > If that is the case, then you should control just that clock directly, > > > outside runtime PM callbacks. > > > > > > Runtime PM may be needed e.g. for resuming a parent device. > > > > Exactly. > > So probably you should not consider R9A07G044_CRU_VCLK a PM clock, > > i.e. you need changes to rzg2l_cpg_is_pm_clk() to exclude it. > > > Thanks for the pointer. In that case we will have to consider > R9A07G044_CRU_VCLK and R9A07G044_CRU_SYSCLK as not PM clocks. > > Does the below sound good? > - DEF_NO_PM() macro > - bool is_pm_clk in struct rzg2l_mod_clk. > > I still have to implement it, just wanted your opinion beforehand. LGTM. Thanks! 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
Hi Laurent, Thank you for the review. On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > Hi Prabhakar, > > Thank you for the patch. > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > Based on a patch in the BSP by Hien Huynh > > <hien.huynh.px@renesas.com> > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > --- > > v1 -> v2 > > * No change > > > > RFC v2 -> v1 > > * Moved the driver to renesas folder > > * Fixed review comments pointed by Jacopo > > > > RFC v1 -> RFC v2 > > * Dropped group > > * Dropped CSI subdev and implemented as new driver > > * Dropped "mc_" from function names > > * Moved the driver to renesas folder > > --- > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > I'd merge those two files together, they both handle the video node. > There's a comment below that recommends adding a subdev, that should > then go to a separate file. > OK, I'll merge these files into rzg2l-video.c. > > 6 files changed, 1669 insertions(+) > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > index 57c40bb499df..08ff0e96b3f5 100644 > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > To compile this driver as a module, choose M here: the > > module will be called rzg2l-csi2. > > + > > +config VIDEO_RZG2L_CRU > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > + depends on ARCH_RENESAS || COMPILE_TEST > > + depends on V4L_PLATFORM_DRIVERS > > + depends on VIDEO_DEV && OF > > + select MEDIA_CONTROLLER > > + select V4L2_FWNODE > > + select VIDEOBUF2_DMA_CONTIG > > + select VIDEO_RZG2L_CSI2 > > Is this required, can't the CRU be used with a parallel sensor without > the CSI-2 receiver ? > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > + select VIDEO_V4L2_SUBDEV_API > > + help > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > + Unit (CRU) driver. > > + > > + To compile this driver as a module, choose M here: the > > + module will be called rzg2l-cru. > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > index 91ea97a944e6..7628809e953f 100644 > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > @@ -1,3 +1,6 @@ > > # SPDX-License-Identifier: GPL-2.0 > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > + > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > new file mode 100644 > > index 000000000000..b5d4110b1913 > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > @@ -0,0 +1,395 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > +/* > > + * Driver for Renesas RZ/G2L CRU > > + * > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + * > > + * Based on Renesas R-Car VIN > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > + * Copyright (C) 2008 Magnus Damm > > + */ > > + > > +#include <linux/clk.h> > > +#include <linux/module.h> > > +#include <linux/mod_devicetable.h> > > +#include <linux/of.h> > > +#include <linux/of_device.h> > > +#include <linux/of_graph.h> > > +#include <linux/platform_device.h> > > +#include <linux/pm_runtime.h> > > + > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mc.h> > > + > > +#include "rzg2l-cru.h" > > + > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > As this macro is only used to get the rzg2l_cru_dev pointer from the > v4l2_async_notifier pointer, you can replace it with > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > I would also turn it into a static inline function for additional > compile-time type safety. > OK, I will do it as mentioned above. > > + > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > + unsigned int notification) > > +{ > > + struct media_entity *entity; > > + struct rzg2l_cru_dev *cru; > > + struct media_pad *csi_pad; > > + struct v4l2_subdev *sd; > > + int ret; > > + > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > + if (ret) > > + return ret; > > + > > + /* Only care about link enablement for CRU nodes. */ > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > + return 0; > > + > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > + /* > > + * Don't allow link changes if any entity in the graph is > > + * streaming, modifying the CHSEL register fields can disrupt > > + * running streams. > > + */ > > + media_device_for_each_entity(entity, &cru->mdev) > > + if (media_entity_is_streaming(entity)) > > + return -EBUSY; > > + > > + mutex_lock(&cru->mdev_lock); > > + > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > + if (csi_pad) { > > + ret = -EMLINK; > > + goto out; > > + } > > + > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > + if (cru->csi.subdev == sd) { > > + cru->csi.channel = link->source->index - 1; > > + cru->is_csi = true; > > + } else { > > + ret = -ENODEV; > > + } > > + > > +out: > > + mutex_unlock(&cru->mdev_lock); > > + > > + return ret; > > +} > > + > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > + .link_notify = rzg2l_cru_csi2_link_notify, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Group async notifier > > + */ > > + > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > +{ > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > + unsigned int i; > > + int ret; > > + > > + ret = media_device_register(&cru->mdev); > > + if (ret) > > + return ret; > > I'd move the v4l2_device_register() call here, as it's the V4L2 > counterpart of the media device, and handling them together would be > best. > OK. > > + > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > + if (ret) { > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > + return ret; > > + } > > + > > + if (!video_is_registered(&cru->vdev)) { > > Can this happen ? > No, I'll drop this check. > > + ret = rzg2l_cru_v4l2_register(cru); > > + if (ret) > > + return ret; > > + } > > + > > + /* Create all media device links between CRU and CSI-2's. */ > > + /* > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > + * should be implemented by streams API which is under development > > + * so for now just link it to VC0 > > + */ > > The streams API won't require more links, so I'd drop the comment and > the loop and create a single link. > OK. > > + for (i = 1; i <= 1; i++) { > > + struct media_entity *source, *sink; > > + > > + source = &cru->csi.subdev->entity; > > + sink = &cru->vdev.entity; > > Hmmm... I'd recommend adding a subdev to model the image processing > pipeline of the CRU, between the CSI-2 receiver and the video node. That > will help when you'll add support for parallel sensors, and it will also > be needed by the streams API to select which virtual channel to capture. > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? > > + > > + ret = media_create_pad_link(source, i, sink, 0, 0); > > + if (ret) { > > + dev_err(cru->dev, "Error adding link from %s to %s\n", > > + source->name, sink->name); > > + break; > > + } > > + } > > + > > + return ret; > > +} > > + > > +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *subdev, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > + > > + rzg2l_cru_v4l2_unregister(cru); > > + > > + mutex_lock(&cru->mdev_lock); > > + > > + if (cru->csi.asd == asd) { > > + cru->csi.subdev = NULL; > > + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); > > + } > > + > > + mutex_unlock(&cru->mdev_lock); > > + > > + media_device_unregister(&cru->mdev); > > +} > > + > > +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, > > + struct v4l2_subdev *subdev, > > + struct v4l2_async_subdev *asd) > > +{ > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > + > > + mutex_lock(&cru->mdev_lock); > > + > > + if (cru->csi.asd == asd) { > > + cru->csi.subdev = subdev; > > + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); > > + } > > + > > + mutex_unlock(&cru->mdev_lock); > > + > > + return 0; > > +} > > + > > +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { > > + .bound = rzg2l_cru_group_notify_bound, > > + .unbind = rzg2l_cru_group_notify_unbind, > > + .complete = rzg2l_cru_group_notify_complete, > > +}; > > + > > +static int rvin_mc_parse_of(struct rzg2l_cru_dev *cru, unsigned int id) > > The id parameter is always 0, I'd drop it. > Agreed, I will drop it. > > +{ > > + struct v4l2_fwnode_endpoint vep = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > + }; > > + struct fwnode_handle *ep, *fwnode; > > + struct v4l2_async_subdev *asd; > > + int ret; > > + > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, id, 0); > > + if (!ep) > > + return 0; > > + > > + fwnode = fwnode_graph_get_remote_endpoint(ep); > > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > > + fwnode_handle_put(ep); > > + if (ret) { > > + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); > > + ret = -EINVAL; > > + goto out; > > + } > > + > > + if (!of_device_is_available(to_of_node(fwnode))) { > > + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", > > + to_of_node(fwnode)); > > + ret = -ENOTCONN; > > + goto out; > > + } > > + > > + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, > > + struct v4l2_async_subdev); > > + if (IS_ERR(asd)) { > > + ret = PTR_ERR(asd); > > + goto out; > > + } > > + > > + cru->csi.asd = asd; > > + > > + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", > > + to_of_node(fwnode), vep.base.id); > > +out: > > + fwnode_handle_put(fwnode); > > + > > + return ret; > > +} > > + > > +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) > > +{ > > + int ret; > > + > > + v4l2_async_nf_init(&cru->notifier); > > + > > + ret = rvin_mc_parse_of(cru, 0); > > + if (ret) > > + return ret; > > + > > + cru->notifier.ops = &rzg2l_cru_async_ops; > > + > > + if (list_empty(&cru->notifier.asd_list)) > > + return 0; > > + > > + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); > > + if (ret < 0) { > > + dev_err(cru->dev, "Notifier registration failed\n"); > > + v4l2_async_nf_cleanup(&cru->notifier); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_csi2_init(struct rzg2l_cru_dev *cru) > > The naming is a bit weird, as this isn't related to CSI-2. I would name > the function rzg2l_cru_media_init(). > Agreed, I will rename it. > > +{ > > + struct media_device *mdev = NULL; > > + const struct of_device_id *match; > > + int ret; > > + > > + cru->pad.flags = MEDIA_PAD_FL_SINK; > > + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); > > + if (ret) > > + return ret; > > + > > + mutex_init(&cru->mdev_lock); > > + mdev = &cru->mdev; > > + mdev->dev = cru->dev; > > + mdev->ops = &rzg2l_cru_media_ops; > > + > > + match = of_match_node(cru->dev->driver->of_match_table, > > + cru->dev->of_node); > > + > > + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); > > + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); > > + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", > > + dev_name(mdev->dev)); > > + > > + cru->v4l2_dev.mdev = &cru->mdev; > > + > > + media_device_init(mdev); > > + > > + ret = rzg2l_cru_mc_parse_of_graph(cru); > > + if (ret) { > > + mutex_lock(&cru->mdev_lock); > > + cru->v4l2_dev.mdev = NULL; > > + mutex_unlock(&cru->mdev_lock); > > + } > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_probe(struct platform_device *pdev) > > +{ > > + struct rzg2l_cru_dev *cru; > > + int irq, ret; > > + > > + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); > > + if (!cru) > > + return -ENOMEM; > > + > > + cru->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(cru->base)) > > + return PTR_ERR(cru->base); > > + > > + cru->presetn = devm_reset_control_get(&pdev->dev, "presetn"); > > + if (IS_ERR(cru->presetn)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), > > + "failed to get cpg presetn\n"); > > + > > + cru->aresetn = devm_reset_control_get(&pdev->dev, "aresetn"); > > + if (IS_ERR(cru->aresetn)) > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), > > + "failed to get cpg aresetn\n"); > > + > > + cru->vclk = devm_clk_get(&pdev->dev, "vclk"); > > + if (IS_ERR(cru->vclk)) { > > + dev_err(&pdev->dev, "Failed to get vclk"); > > + return PTR_ERR(cru->vclk); > > You could use dev_err_probe() here too (as well as below). > OK. > > + } > > + > > + cru->pclk = devm_clk_get(&pdev->dev, "pclk"); > > + if (IS_ERR(cru->pclk)) { > > + dev_err(&pdev->dev, "Failed to get pclk"); > > + return PTR_ERR(cru->pclk); > > + } > > + > > + cru->aclk = devm_clk_get(&pdev->dev, "aclk"); > > + if (IS_ERR(cru->aclk)) { > > + dev_err(&pdev->dev, "Failed to get aclk"); > > + return PTR_ERR(cru->aclk); > > + } > > + > > + cru->dev = &pdev->dev; > > + cru->info = of_device_get_match_data(&pdev->dev); > > + > > + irq = platform_get_irq(pdev, 0); > > + if (irq < 0) > > + return irq; > > + > > + ret = rzg2l_cru_dma_register(cru, irq); > > + if (ret) > > + return ret; > > + > > + platform_set_drvdata(pdev, cru); > > + > > + ret = rzg2l_cru_csi2_init(cru); > > + if (ret) > > + goto error_dma_unregister; > > + > > + cru->num_buf = HW_BUFFER_DEFAULT; > > + pm_suspend_ignore_children(&pdev->dev, true); > > + pm_runtime_enable(&pdev->dev); > > + > > + return 0; > > + > > +error_dma_unregister: > > + rzg2l_cru_dma_unregister(cru); > > + > > + return ret; > > +} > > + > > +static const struct of_device_id rzg2l_cru_of_id_table[] = { > > + { > > + .compatible = "renesas,rzg2l-cru", > > + }, > > + { /* sentinel */ } > > +}; > > +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); > > + > > +static int rzg2l_cru_remove(struct platform_device *pdev) > > +{ > > + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); > > + > > + pm_runtime_disable(&pdev->dev); > > + > > + rzg2l_cru_v4l2_unregister(cru); > > + > > + v4l2_async_nf_unregister(&cru->notifier); > > + v4l2_async_nf_cleanup(&cru->notifier); > > + > > + media_device_cleanup(&cru->mdev); > > + mutex_destroy(&cru->mdev_lock); > > + cru->v4l2_dev.mdev = NULL; > > Is this needed ? > Not required. > > + > > + rzg2l_cru_dma_unregister(cru); > > + > > + return 0; > > +} > > + > > +static struct platform_driver rzg2l_cru_driver = { > > + .driver = { > > + .name = "rzg2l-cru", > > + .of_match_table = rzg2l_cru_of_id_table, > > + }, > > + .probe = rzg2l_cru_probe, > > + .remove = rzg2l_cru_remove, > > No PM ? > I plan to gradually add at a later point. > > +}; > > + > > +module_platform_driver(rzg2l_cru_driver); > > + > > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > > +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); > > +MODULE_LICENSE("GPL"); > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > new file mode 100644 > > index 000000000000..a834680a3200 > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > @@ -0,0 +1,152 @@ > > +/* SPDX-License-Identifier: GPL-2.0+ */ > > +/* > > + * Driver for Renesas RZ/G2L CRU > > + * > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + * > > Extra blank line. > Oops, I will drop it. > > + */ > > + > > +#ifndef __RZG2L_CRU__ > > +#define __RZG2L_CRU__ > > + > > +#include <linux/reset.h> > > + > > +#include <media/v4l2-async.h> > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-dev.h> > > +#include <media/v4l2-device.h> > > +#include <media/videobuf2-v4l2.h> > > + > > +/* Number of HW buffers */ > > +#define HW_BUFFER_MAX 8 > > +#define HW_BUFFER_DEFAULT 3 > > Could you prefix macro names with CRU_ (or RZG2L_CRU_, up to you) ? > These names are a bit generic and could lead to clashes. > Agreed, I will rename it. > > + > > +/* Address alignment mask for HW buffers */ > > +#define HW_BUFFER_MASK 0x1ff > > + > > +/* Maximum number of CSI2 virtual channels */ > > +#define CSI2_VCHANNEL 4 > > + > > +#define CRU_MAX_INPUT_WIDTH 2800 > > +#define CRU_MAX_INPUT_HEIGHT 4095 > > + > > +/** > > + * enum rzg2l_cru_dma_state - DMA states > > + * @RZG2L_CRU_DMA_STOPPED: No operation in progress > > + * @RZG2L_CRU_DMA_STARTING: Capture starting up > > + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers > > + * @RZG2L_CRU_DMA_STOPPING: Stopping operation > > + */ > > +enum rzg2l_cru_dma_state { > > + RZG2L_CRU_DMA_STOPPED = 0, > > + RZG2L_CRU_DMA_STARTING, > > + RZG2L_CRU_DMA_RUNNING, > > + RZG2L_CRU_DMA_STOPPING, > > +}; > > + > > +struct rzg2l_cru_csi { > > + struct v4l2_async_subdev *asd; > > + struct v4l2_subdev *subdev; > > + u32 channel; > > +}; > > + > > +/** > > + * struct rzg2l_cru_dev - Renesas CRU device structure > > + * @dev: (OF) device > > + * @base: device I/O register space remapped to virtual memory > > + * @info: info about CRU instance > > + * > > + * @presetn: CRU_PRESETN reset line > > + * @aresetn: CRU_ARESETN reset line > > + * > > + * @vclk: CRU Main clock > > + * @pclk: CPU Register access clock > > + * @aclk: CRU image transfer clock > > + * > > + * @vdev: V4L2 video device associated with CRU > > + * @v4l2_dev: V4L2 device > > + * @ctrl_handler: V4L2 control handler > > + * @num_buf: Holds the current number of buffers enabled > > + * @notifier: V4L2 asynchronous subdevs notifier > > + * > > + * @csi: CSI info > > + * @mdev: media device > > + * @mdev_lock: protects the count, notifier and csi members > > + * @pad: media pad for the video device entity > > + * > > + * @lock: protects @queue > > + * @queue: vb2 buffers queue > > + * @scratch: cpu address for scratch buffer > > + * @scratch_phys: physical address of the scratch buffer > > + * > > + * @qlock: protects @queue_buf, @buf_list, @sequence > > + * @state > > + * @queue_buf: Keeps track of buffers given to HW slot > > + * @buf_list: list of queued buffers > > + * @sequence: V4L2 buffers sequence number > > + * @state: keeps track of operation state > > + * > > + * @is_csi: flag to mark the CRU as using a CSI-2 subdevice > > + * > > + * @input_is_yuv: flag to mark the input format of CRU > > + * @output_is_yuv: flag to mark the output format of CRU > > + * > > + * @mbus_code: media bus format code > > + * @format: active V4L2 pixel format > > + * > > + * @compose: active composing > > + */ > > +struct rzg2l_cru_dev { > > + struct device *dev; > > + void __iomem *base; > > + const struct rzg2l_cru_info *info; > > + > > + struct reset_control *presetn; > > + struct reset_control *aresetn; > > + > > + struct clk *vclk; > > + struct clk *pclk; > > + struct clk *aclk; > > + > > + struct video_device vdev; > > + struct v4l2_device v4l2_dev; > > + u8 num_buf; > > + > > + struct v4l2_async_notifier notifier; > > + > > + struct rzg2l_cru_csi csi; > > + struct media_device mdev; > > + struct mutex mdev_lock; > > + struct media_pad pad; > > + > > + struct mutex lock; > > + struct vb2_queue queue; > > + void *scratch; > > + dma_addr_t scratch_phys; > > + > > + spinlock_t qlock; > > + struct vb2_v4l2_buffer *queue_buf[HW_BUFFER_MAX]; > > + struct list_head buf_list; > > + unsigned int sequence; > > + enum rzg2l_cru_dma_state state; > > + > > + bool is_csi; > > + > > + bool input_is_yuv; > > + bool output_is_yuv; > > + > > + u32 mbus_code; > > + struct v4l2_pix_format format; > > + > > + struct v4l2_rect compose; > > +}; > > + > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq); > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); > > + > > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru); > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru); > > + > > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); > > + > > +#endif > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > new file mode 100644 > > index 000000000000..44efd071f562 > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > @@ -0,0 +1,734 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > +/* > > + * Driver for Renesas RZ/G2L CRU > > + * > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + * > > + * Based on Renesas R-Car VIN > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > + * Copyright (C) 2008 Magnus Damm > > + */ > > + > > +#include <linux/clk.h> > > +#include <linux/delay.h> > > +#include <linux/interrupt.h> > > + > > +#include <media/videobuf2-dma-contig.h> > > + > > +#include "rzg2l-cru.h" > > +#include "rzg2l-csi2.h" > > + > > +/* HW CRU Registers Definition */ > > +/* CRU Control Register */ > > +#define CRUnCTRL 0x0 > > +#define CRUnCTRL_VINSEL(x) ((x) << 0) > > + > > +/* CRU Interrupt Enable Register */ > > +#define CRUnIE 0x4 > > +#define CRUnIE_SFE BIT(16) > > +#define CRUnIE_EFE BIT(17) > > + > > +/* CRU Interrupt Status Register */ > > +#define CRUnINTS 0x8 > > +#define CRUnINTS_SFS BIT(16) > > + > > +/* CRU Reset Register */ > > +#define CRUnRST 0xc > > +#define CRUnRST_VRESETN BIT(0) > > + > > +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ > > +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) > > + > > +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ > > +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) > > + > > +/* Memory Bank Enable Register for CRU Image Data */ > > +#define AMnMBVALID 0x148 > > +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) > > + > > +/* Memory Bank Status Register for CRU Image Data */ > > +#define AMnMBS 0x14c > > +#define AMnMBS_MBSTS 0x7 > > + > > +/* AXI Master FIFO Pointer Register for CRU Image Data */ > > +#define AMnFIFOPNTR 0x168 > > +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) > > +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) > > + > > +/* AXI Master Transfer Stop Register for CRU Image Data */ > > +#define AMnAXISTP 0x174 > > +#define AMnAXISTP_AXI_STOP BIT(0) > > + > > +/* AXI Master Transfer Stop Status Register for CRU Image Data */ > > +#define AMnAXISTPACK 0x178 > > +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) > > + > > +/* CRU Image Processing Enable Register */ > > +#define ICnEN 0x200 > > +#define ICnEN_ICEN BIT(0) > > + > > +/* CRU Image Processing Main Control Register */ > > +#define ICnMC 0x208 > > +#define ICnMC_CSCTHR BIT(5) > > +#define ICnMC_INF_YUV8_422 (0x1e << 16) > > +#define ICnMC_INF_USER (0x30 << 16) > > +#define ICnMC_VCSEL(x) ((x) << 22) > > +#define ICnMC_INF_MASK GENMASK(21, 16) > > + > > +/* CRU Module Status Register */ > > +#define ICnMS 0x254 > > +#define ICnMS_IA BIT(2) > > + > > +/* CRU Data Output Mode Register */ > > +#define ICnDMR 0x26c > > +#define ICnDMR_YCMODE_UYVY (1 << 4) > > + > > +#define RZG2L_TIMEOUT_MS 100 > > +#define RZG2L_RETRIES 10 > > + > > +struct rzg2l_cru_buffer { > > + struct vb2_v4l2_buffer vb; > > + struct list_head list; > > +}; > > + > > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > > + struct rzg2l_cru_buffer, \ > > + vb)->list) > > + > > +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) > > +{ > > + iowrite32(value, cru->base + offset); > > +} > > + > > +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) > > +{ > > + return ioread32(cru->base + offset); > > +} > > + > > +/* Need to hold qlock before calling */ > > +static void return_unused_buffers(struct rzg2l_cru_dev *cru, > > + enum vb2_buffer_state state) > > +{ > > + struct rzg2l_cru_buffer *buf, *node; > > + unsigned long flags; > > + unsigned int i; > > + > > + spin_lock_irqsave(&cru->qlock, flags); > > + for (i = 0; i < cru->num_buf; i++) { > > + if (cru->queue_buf[i]) { > > + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, > > + state); > > + cru->queue_buf[i] = NULL; > > + } > > + } > > + > > + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { > > + vb2_buffer_done(&buf->vb.vb2_buf, state); > > + list_del(&buf->list); > > + } > > + spin_unlock_irqrestore(&cru->qlock, flags); > > +} > > + > > +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, > > + unsigned int *nplanes, unsigned int sizes[], > > + struct device *alloc_devs[]) > > +{ > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > + > > + /* Make sure the image size is large enough. */ > > + if (*nplanes) > > + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; > > + > > + *nplanes = 1; > > + sizes[0] = cru->format.sizeimage; > > + > > + return 0; > > +}; > > + > > +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) > > +{ > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > + unsigned long size = cru->format.sizeimage; > > + > > + if (vb2_plane_size(vb, 0) < size) { > > + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", > > + vb2_plane_size(vb, 0), size); > > + return -EINVAL; > > + } > > + > > + vb2_set_plane_payload(vb, 0, size); > > + > > + return 0; > > +} > > + > > +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) > > +{ > > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > + unsigned long flags; > > + > > + spin_lock_irqsave(&cru->qlock, flags); > > + > > + list_add_tail(to_buf_list(vbuf), &cru->buf_list); > > + > > + spin_unlock_irqrestore(&cru->qlock, flags); > > +} > > + > > +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, > > + struct v4l2_subdev *sd, > > + struct media_pad *pad) > > +{ > > + struct v4l2_subdev_format fmt = { > > + .which = V4L2_SUBDEV_FORMAT_ACTIVE, > > + }; > > + > > + fmt.pad = pad->index; > > + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) > > + return -EPIPE; > > + > > + if (cru->is_csi) { > > + switch (fmt.format.code) { > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > + break; > > + default: > > + return -EPIPE; > > + } > > + } > > + cru->mbus_code = fmt.format.code; > > + > > + switch (fmt.format.field) { > > + case V4L2_FIELD_TOP: > > + case V4L2_FIELD_BOTTOM: > > + case V4L2_FIELD_NONE: > > + case V4L2_FIELD_INTERLACED_TB: > > + case V4L2_FIELD_INTERLACED_BT: > > + case V4L2_FIELD_INTERLACED: > > + case V4L2_FIELD_SEQ_TB: > > + case V4L2_FIELD_SEQ_BT: > > + break; > > + default: > > + return -EPIPE; > > + } > > + > > + if (fmt.format.width != cru->format.width || > > + fmt.format.height != cru->format.height || > > + fmt.format.code != cru->mbus_code) > > + return -EPIPE; > > + > > + return 0; > > +} > > + > > +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, > > + int slot, dma_addr_t addr) > > +{ > > + const struct v4l2_format_info *fmt; > > + int offsetx, offsety; > > + dma_addr_t offset; > > + > > + fmt = rzg2l_cru_format_from_pixel(cru->format.pixelformat); > > + > > + /* > > + * There is no HW support for composition do the best we can > > + * by modifying the buffer offset > > + */ > > + offsetx = cru->compose.left * fmt->bpp[0]; > > + offsety = cru->compose.top * cru->format.bytesperline; > > + offset = addr + offsetx + offsety; > > + > > + /* > > + * The address needs to be 512 bytes aligned. Driver should never accept > > + * settings that do not satisfy this in the first place... > > + */ > > + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) > > + return; > > + > > + /* Currently, we just use the buffer in 32 bits address */ > > + rzg2l_cru_write(cru, AMnMBxADDRL(slot), offset); > > + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); > > +} > > + > > +/* > > + * Moves a buffer from the queue to the HW slot. If no buffer is > > + * available use the scratch buffer. The scratch buffer is never > > + * returned to userspace, its only function is to enable the capture > > + * loop to keep running. > > + */ > > +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) > > +{ > > + struct vb2_v4l2_buffer *vbuf; > > + struct rzg2l_cru_buffer *buf; > > + dma_addr_t phys_addr; > > + > > + /* A already populated slot shall never be overwritten. */ > > + if (WARN_ON(cru->queue_buf[slot])) > > + return; > > + > > + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); > > + > > + if (list_empty(&cru->buf_list)) { > > + cru->queue_buf[slot] = NULL; > > + phys_addr = cru->scratch_phys; > > + } else { > > + /* Keep track of buffer we give to HW */ > > + buf = list_entry(cru->buf_list.next, > > + struct rzg2l_cru_buffer, list); > > + vbuf = &buf->vb; > > + list_del_init(to_buf_list(vbuf)); > > + cru->queue_buf[slot] = vbuf; > > + > > + /* Setup DMA */ > > + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); > > + } > > + > > + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); > > +} > > + > > +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) > > +{ > > + unsigned int slot; > > + > > + /* > > + * Set image data memory banks. > > + * Currently, we will use maximum address. > > + */ > > + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); > > + > > + for (slot = 0; slot < cru->num_buf; slot++) > > + rzg2l_cru_fill_hw_slot(cru, slot); > > +} > > + > > +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru) > > +{ > > + u32 icnmc; > > + > > + switch (cru->mbus_code) { > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > + icnmc = ICnMC_INF_YUV8_422; > > + cru->input_is_yuv = true; > > + break; > > + default: > > + cru->input_is_yuv = false; > > + icnmc = ICnMC_INF_USER; > > + break; > > + } > > + > > + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); > > + > > + /* Set virtual channel CSI2 */ > > + icnmc |= ICnMC_VCSEL(cru->csi.channel); > > + > > + rzg2l_cru_write(cru, ICnMC, icnmc); > > +} > > + > > +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru) > > +{ > > + u32 icndmr; > > + > > + if (cru->is_csi) > > + rzg2l_cru_csi2_setup(cru); > > + > > + /* Output format */ > > + switch (cru->format.pixelformat) { > > + case V4L2_PIX_FMT_UYVY: > > + icndmr = ICnDMR_YCMODE_UYVY; > > + cru->output_is_yuv = true; > > + break; > > + default: > > + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", > > + cru->format.pixelformat); > > + return -EINVAL; > > + } > > + > > + /* If input and output use same colorspace, do bypass mode */ > > + if (cru->output_is_yuv == cru->input_is_yuv) > > + rzg2l_cru_write(cru, ICnMC, > > + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); > > + else > > + rzg2l_cru_write(cru, ICnMC, > > + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); > > + > > + /* Set output data format */ > > + rzg2l_cru_write(cru, ICnDMR, icndmr); > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) > > +{ > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > + struct media_pipeline *pipe; > > + struct v4l2_subdev *sd; > > + struct media_pad *pad; > > + unsigned long flags; > > + int ret; > > + > > + pad = media_pad_remote_pad_first(&cru->pad); > > + if (!pad) > > + return -EPIPE; > > + > > + sd = media_entity_to_v4l2_subdev(pad->entity); > > + > > + if (!on) { > > + media_pipeline_stop(&cru->vdev.entity); > > + return v4l2_subdev_call(sd, video, s_stream, 0); > > + } > > + > > + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); > > + if (ret) > > + return ret; > > + > > + rzg2l_csi2_dphy_setting(csi2, 1); > > + > > + spin_lock_irqsave(&cru->qlock, flags); > > + > > + /* Select a video input */ > > + if (cru->is_csi) > > + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); > > + > > + /* Cancel the software reset for image processing block */ > > + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); > > + > > + /* Disable and clear the interrupt before using */ > > + rzg2l_cru_write(cru, CRUnIE, 0); > > + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); > > + > > + /* Initialize the AXI master */ > > + rzg2l_cru_initialize_axi(cru); > > + > > + /* Initialize image convert */ > > + ret = rzg2l_cru_initialize_image_conv(cru); > > + if (ret) { > > + spin_unlock_irqrestore(&cru->qlock, flags); > > + return ret; > > + } > > + > > + /* Enable interrupt */ > > + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); > > + > > + /* Enable image processing reception */ > > + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); > > + > > + spin_unlock_irqrestore(&cru->qlock, flags); > > + > > + pipe = sd->entity.pipe ? sd->entity.pipe : &cru->vdev.pipe; > > + ret = media_pipeline_start(&cru->vdev.entity, pipe); > > + if (ret) > > + return ret; > > + > > + clk_disable_unprepare(cru->vclk); > > + > > + rzg2l_csi2_mipi_link_setting(csi2, 1); > > + > > + ret = clk_prepare_enable(cru->vclk); > > + if (ret) > > + return ret; > > + > > + rzg2l_csi2_cmn_rstb_deassert(csi2); > > + > > + ret = v4l2_subdev_call(sd, video, s_stream, 1); > > + if (ret == -ENOIOCTLCMD) > > + ret = 0; > > + if (ret) > > + media_pipeline_stop(&cru->vdev.entity); > > + > > + return ret; > > +} > > + > > +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) > > +{ > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; > > + unsigned int retries = 0; > > + unsigned long flags; > > + u32 icnms; > > + > > + cru->state = RZG2L_CRU_DMA_STOPPING; > > + > > + rzg2l_cru_set_stream(cru, 0); > > + > > + rzg2l_csi2_dphy_setting(csi2, 0); > > + > > + rzg2l_csi2_mipi_link_setting(csi2, 0); > > + > > + spin_lock_irqsave(&cru->qlock, flags); > > + > > + /* Disable and clear the interrupt */ > > + rzg2l_cru_write(cru, CRUnIE, 0); > > + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); > > + > > + /* Stop the operation of image conversion */ > > + rzg2l_cru_write(cru, ICnEN, 0); > > + > > + /* Wait for streaming to stop */ > > + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { > > + spin_unlock_irqrestore(&cru->qlock, flags); > > + msleep(RZG2L_TIMEOUT_MS); > > + spin_lock_irqsave(&cru->qlock, flags); > > + } > > + > > + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; > > + if (icnms) > > + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); > > + > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > + > > + /* Wait until the FIFO becomes empty */ > > + for (retries = 5; retries > 0; retries--) { > > + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); > > + > > + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; > > + amnfifopntr_r_y = > > + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; > > + if (amnfifopntr_w == amnfifopntr_r_y) > > + break; > > + > > + usleep_range(10, 20); > > + } > > + > > + /* Notify that FIFO is not empty here */ > > + if (!retries) > > + dev_err(cru->dev, "Failed to empty FIFO\n"); > > + > > + /* Stop AXI bus */ > > + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); > > + > > + /* Wait until the AXI bus stop */ > > + for (retries = 5; retries > 0; retries--) { > > + if (rzg2l_cru_read(cru, AMnAXISTPACK) & > > + AMnAXISTPACK_AXI_STOP_ACK) > > + break; > > + > > + usleep_range(10, 20); > > + }; > > + > > + /* Notify that AXI bus can not stop here */ > > + if (!retries) > > + dev_err(cru->dev, "Failed to stop AXI bus\n"); > > + > > + /* Cancel the AXI bus stop request */ > > + rzg2l_cru_write(cru, AMnAXISTP, 0); > > + > > + /* Resets the image processing module */ > > + rzg2l_cru_write(cru, CRUnRST, 0); > > + > > + spin_unlock_irqrestore(&cru->qlock, flags); > > + > > + /* Set reset state */ > > + reset_control_assert(cru->aresetn); > > +} > > + > > +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) > > +{ > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > + int ret; > > + > > + /* Release reset state */ > > + ret = reset_control_deassert(cru->aresetn); > > + if (ret) { > > + dev_err(cru->dev, "failed to deassert aresetn\n"); > > + return ret; > > + } > > + > > + /* Allocate scratch buffer. */ > > + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, > > + &cru->scratch_phys, GFP_KERNEL); > > + if (!cru->scratch) { > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); > > + return -ENOMEM; > > + } > > + > > + cru->sequence = 0; > > + > > + ret = rzg2l_cru_set_stream(cru, 1); > > + if (ret) { > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > + goto out; > > + } > > + > > + cru->state = RZG2L_CRU_DMA_STARTING; > > + > > + dev_dbg(cru->dev, "Starting to capture\n"); > > + > > +out: > > + if (ret) > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > + cru->scratch_phys); > > + > > + return ret; > > +} > > + > > +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) > > +{ > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > + > > + rzg2l_cru_stop_streaming(cru); > > + > > + /* Free scratch buffer */ > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > + cru->scratch_phys); > > + > > + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); > > +} > > + > > +static const struct vb2_ops rzg2l_cru_qops = { > > + .queue_setup = rzg2l_cru_queue_setup, > > + .buf_prepare = rzg2l_cru_buffer_prepare, > > + .buf_queue = rzg2l_cru_buffer_queue, > > + .start_streaming = rzg2l_cru_start_streaming_vq, > > + .stop_streaming = rzg2l_cru_stop_streaming_vq, > > + .wait_prepare = vb2_ops_wait_prepare, > > + .wait_finish = vb2_ops_wait_finish, > > +}; > > + > > +static irqreturn_t rzg2l_cru_irq(int irq, void *data) > > +{ > > + struct rzg2l_cru_dev *cru = data; > > + unsigned int handled = 0; > > + unsigned long flags; > > + u32 irq_status; > > + u32 amnmbs; > > + int slot; > > + > > + spin_lock_irqsave(&cru->qlock, flags); > > + > > + irq_status = rzg2l_cru_read(cru, CRUnINTS); > > + if (!irq_status) > > + goto done; > > + > > + handled = 1; > > + > > + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); > > + > > + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ > > + if (cru->state == RZG2L_CRU_DMA_STOPPED) { > > + dev_dbg(cru->dev, "IRQ while state stopped\n"); > > + goto done; > > + } > > + > > + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ > > + if (cru->state == RZG2L_CRU_DMA_STOPPING) { > > + if (irq_status & CRUnINTS_SFS) > > + dev_dbg(cru->dev, "IRQ while state stopping\n"); > > + goto done; > > + } > > + > > + /* Prepare for capture and update state */ > > + amnmbs = rzg2l_cru_read(cru, AMnMBS); > > + slot = amnmbs & AMnMBS_MBSTS; > > + > > + /* > > + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). > > + * Recalculate to get the current transfer complete MB. > > + */ > > + if (slot == 0) > > + slot = cru->num_buf - 1; > > + else > > + slot--; > > + > > + /* > > + * To hand buffers back in a known order to userspace start > > + * to capture first from slot 0. > > + */ > > + if (cru->state == RZG2L_CRU_DMA_STARTING) { > > + if (slot != 0) { > > + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); > > + goto done; > > + } > > + > > + dev_dbg(cru->dev, "Capture start synced!\n"); > > + cru->state = RZG2L_CRU_DMA_RUNNING; > > + } > > + > > + /* Capture frame */ > > + if (cru->queue_buf[slot]) { > > + cru->queue_buf[slot]->field = cru->format.field; > > + cru->queue_buf[slot]->sequence = cru->sequence; > > + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); > > + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, > > + VB2_BUF_STATE_DONE); > > + cru->queue_buf[slot] = NULL; > > + } else { > > + /* Scratch buffer was used, dropping frame. */ > > + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); > > + } > > + > > + cru->sequence++; > > + > > + /* Prepare for next frame */ > > + rzg2l_cru_fill_hw_slot(cru, slot); > > + > > +done: > > + spin_unlock_irqrestore(&cru->qlock, flags); > > + > > + return IRQ_RETVAL(handled); > > +} > > + > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) > > +{ > > + mutex_destroy(&cru->lock); > > + > > + v4l2_device_unregister(&cru->v4l2_dev); > > + reset_control_assert(cru->presetn); > > +} > > + > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq) > > +{ > > + struct vb2_queue *q = &cru->queue; > > + unsigned int i; > > + int ret; > > + > > + ret = reset_control_deassert(cru->presetn); > > + if (ret) { > > + dev_err(cru->dev, "failed to deassert presetn\n"); > > + return ret; > > + } > > Shouldn't this be done when starting streaming instead ? > Agreed, Ill move it there. > > + > > + /* Initialize the top-level structure */ > > + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); > > + if (ret) > > + return ret; > > + > > + mutex_init(&cru->lock); > > + INIT_LIST_HEAD(&cru->buf_list); > > + > > + spin_lock_init(&cru->qlock); > > + > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > + > > + for (i = 0; i < HW_BUFFER_MAX; i++) > > + cru->queue_buf[i] = NULL; > > + > > + /* buffer queue */ > > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > > + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; > > No VB2_READ please, that's very inefficient. > OK, I'll drop it. > > + q->lock = &cru->lock; > > + q->drv_priv = cru; > > + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); > > + q->ops = &rzg2l_cru_qops; > > + q->mem_ops = &vb2_dma_contig_memops; > > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > > + q->min_buffers_needed = 4; > > Does the hardware really require 4 buffers to operate ? > v4l2-compliance complains about sequnce mismatch when set to 3. > > + q->dev = cru->dev; > > + > > + ret = vb2_queue_init(q); > > + if (ret < 0) { > > + dev_err(cru->dev, "failed to initialize VB2 queue\n"); > > + goto error; > > + } > > + > > + /* IRQ */ > > + ret = devm_request_irq(cru->dev, irq, rzg2l_cru_irq, IRQF_SHARED, > > + KBUILD_MODNAME, cru); > > + if (ret) { > > + dev_err(cru->dev, "failed to request irq\n"); > > + goto error; > > + } > > + > > + return 0; > > + > > +error: > > + rzg2l_cru_dma_unregister(cru); > > + return ret; > > +} > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > new file mode 100644 > > index 000000000000..c565597f5769 > > --- /dev/null > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > @@ -0,0 +1,368 @@ > > +// SPDX-License-Identifier: GPL-2.0+ > > +/* > > + * Driver for Renesas RZ/G2L CRU > > + * > > + * Copyright (C) 2022 Renesas Electronics Corp. > > + * > > + * Based on Renesas R-Car VIN > > + * Copyright (C) 2016 Renesas Electronics Corp. > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > + * Copyright (C) 2008 Magnus Damm > > + */ > > + > > +#include <linux/clk.h> > > + > > +#include <media/v4l2-event.h> > > +#include <media/v4l2-ioctl.h> > > +#include <media/v4l2-mc.h> > > +#include <media/v4l2-rect.h> > > + > > +#include "rzg2l-cru.h" > > + > > +#define RZG2L_CRU_DEFAULT_FORMAT V4L2_PIX_FMT_UYVY > > +#define RZG2L_CRU_DEFAULT_WIDTH 800 > > +#define RZG2L_CRU_DEFAULT_HEIGHT 600 > > +#define RZG2L_CRU_DEFAULT_FIELD V4L2_FIELD_NONE > > +#define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB > > + > > +/* ----------------------------------------------------------------------------- > > + * Format Conversions > > + */ > > + > > +static const struct v4l2_format_info rzg2l_cru_formats[] = { > > + { > > + .format = V4L2_PIX_FMT_UYVY, > > + .bpp[0] = 2, > > + }, > > +}; > > + > > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format) > > +{ > > + unsigned int i; > > + > > + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_formats); i++) > > + if (rzg2l_cru_formats[i].format == format) > > + return rzg2l_cru_formats + i; > > + > > + return NULL; > > +} > > + > > +static u32 rzg2l_cru_format_bytesperline(struct v4l2_pix_format *pix) > > +{ > > + const struct v4l2_format_info *fmt; > > + > > + fmt = rzg2l_cru_format_from_pixel(pix->pixelformat); > > + > > + if (WARN_ON(!fmt)) > > + return -EINVAL; > > + > > + return pix->width * fmt->bpp[0]; > > +} > > + > > +static u32 rzg2l_cru_format_sizeimage(struct v4l2_pix_format *pix) > > +{ > > + return pix->bytesperline * pix->height; > > +} > > + > > +static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, > > + struct v4l2_pix_format *pix) > > +{ > > + if (!rzg2l_cru_format_from_pixel(pix->pixelformat)) > > + pix->pixelformat = RZG2L_CRU_DEFAULT_FORMAT; > > + > > + switch (pix->field) { > > + case V4L2_FIELD_TOP: > > + case V4L2_FIELD_BOTTOM: > > + case V4L2_FIELD_NONE: > > + case V4L2_FIELD_INTERLACED_TB: > > + case V4L2_FIELD_INTERLACED_BT: > > + case V4L2_FIELD_INTERLACED: > > + break; > > + default: > > + pix->field = RZG2L_CRU_DEFAULT_FIELD; > > + break; > > + } > > + > > + /* Limit to CRU capabilities */ > > + v4l_bound_align_image(&pix->width, 320, CRU_MAX_INPUT_WIDTH, 1, > > + &pix->height, 240, CRU_MAX_INPUT_HEIGHT, 2, 0); > > + > > + pix->bytesperline = rzg2l_cru_format_bytesperline(pix); > > + pix->sizeimage = rzg2l_cru_format_sizeimage(pix); > > + > > + dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", > > + pix->width, pix->height, pix->bytesperline, pix->sizeimage); > > +} > > + > > +static void rzg2l_cru_try_format(struct rzg2l_cru_dev *cru, > > + struct v4l2_pix_format *pix) > > +{ > > + /* > > + * The V4L2 specification clearly documents the colorspace fields > > + * as being set by drivers for capture devices. Using the values > > + * supplied by userspace thus wouldn't comply with the API. Until > > + * the API is updated force fixed values. > > + */ > > + pix->colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; > > + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); > > + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); > > + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, > > + pix->ycbcr_enc); > > + > > + rzg2l_cru_format_align(cru, pix); > > +} > > + > > +static int rzg2l_cru_querycap(struct file *file, void *priv, > > + struct v4l2_capability *cap) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + > > + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); > > + strscpy(cap->card, "RZG2L_CRU", sizeof(cap->card)); > > + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", > > + dev_name(cru->dev)); > > + return 0; > > +} > > + > > +static int rzg2l_cru_try_fmt_vid_cap(struct file *file, void *priv, > > + struct v4l2_format *f) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + > > + rzg2l_cru_try_format(cru, &f->fmt.pix); > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_s_fmt_vid_cap(struct file *file, void *priv, > > + struct v4l2_format *f) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + > > + if (vb2_is_busy(&cru->queue)) > > + return -EBUSY; > > + > > + rzg2l_cru_try_format(cru, &f->fmt.pix); > > + > > + cru->format = f->fmt.pix; > > + > > + cru->compose.top = 0; > > + cru->compose.left = 0; > > + cru->compose.width = cru->format.width; > > + cru->compose.height = cru->format.height; > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_g_fmt_vid_cap(struct file *file, void *priv, > > + struct v4l2_format *f) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + > > + f->fmt.pix = cru->format; > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_enum_fmt_vid_cap(struct file *file, void *priv, > > + struct v4l2_fmtdesc *f) > > +{ > > + if (f->index >= ARRAY_SIZE(rzg2l_cru_formats)) > > + return -EINVAL; > > + > > + f->pixelformat = rzg2l_cru_formats[f->index].format; > > + > > + return 0; > > +} > > + > > +static int rzg2l_cru_subscribe_event(struct v4l2_fh *fh, > > + const struct v4l2_event_subscription *sub) > > +{ > > + switch (sub->type) { > > + case V4L2_EVENT_SOURCE_CHANGE: > > + return v4l2_event_subscribe(fh, sub, 4, NULL); > > + } > > + return v4l2_ctrl_subscribe_event(fh, sub); > > +} > > + > > +static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { > > + .vidioc_querycap = rzg2l_cru_querycap, > > + .vidioc_try_fmt_vid_cap = rzg2l_cru_try_fmt_vid_cap, > > + .vidioc_g_fmt_vid_cap = rzg2l_cru_g_fmt_vid_cap, > > + .vidioc_s_fmt_vid_cap = rzg2l_cru_s_fmt_vid_cap, > > + .vidioc_enum_fmt_vid_cap = rzg2l_cru_enum_fmt_vid_cap, > > + > > + .vidioc_reqbufs = vb2_ioctl_reqbufs, > > + .vidioc_create_bufs = vb2_ioctl_create_bufs, > > + .vidioc_querybuf = vb2_ioctl_querybuf, > > + .vidioc_qbuf = vb2_ioctl_qbuf, > > + .vidioc_dqbuf = vb2_ioctl_dqbuf, > > + .vidioc_expbuf = vb2_ioctl_expbuf, > > + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, > > + .vidioc_streamon = vb2_ioctl_streamon, > > + .vidioc_streamoff = vb2_ioctl_streamoff, > > + > > + .vidioc_log_status = v4l2_ctrl_log_status, > > + .vidioc_subscribe_event = rzg2l_cru_subscribe_event, > > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, > > +}; > > + > > +/* ----------------------------------------------------------------------------- > > + * Media controller file operations > > + */ > > + > > +static int rzg2l_cru_open(struct file *file) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + int ret; > > + > > + ret = clk_prepare_enable(cru->pclk); > > + if (ret) > > + return ret; > > + > > + ret = clk_prepare_enable(cru->vclk); > > + if (ret) > > + goto disable_pclk; > > + > > + ret = clk_prepare_enable(cru->aclk); > > + if (ret) > > + goto disable_vclk; > > + > > + ret = mutex_lock_interruptible(&cru->lock); > > + if (ret) > > + goto disable_aclk; > > + > > + file->private_data = cru; > > + ret = v4l2_fh_open(file); > > + if (ret) > > + goto err_unlock; > > + > > + ret = v4l2_pipeline_pm_get(&cru->vdev.entity); > > + if (ret < 0) > > + goto err_open; > > + > > + mutex_unlock(&cru->lock); > > + > > + return 0; > > +err_open: > > + v4l2_fh_release(file); > > +err_unlock: > > + mutex_unlock(&cru->lock); > > +disable_aclk: > > + clk_disable_unprepare(cru->aclk); > > +disable_vclk: > > + clk_disable_unprepare(cru->vclk); > > +disable_pclk: > > + clk_disable_unprepare(cru->pclk); > > + > > + return ret; > > +} > > + > > +static int rzg2l_cru_release(struct file *file) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + int ret; > > + > > + mutex_lock(&cru->lock); > > + > > + /* the release helper will cleanup any on-going streaming. */ > > + ret = _vb2_fop_release(file, NULL); > > + > > + v4l2_pipeline_pm_put(&cru->vdev.entity); > > + clk_disable_unprepare(cru->aclk); > > + clk_disable_unprepare(cru->vclk); > > + clk_disable_unprepare(cru->pclk); > > + > > + mutex_unlock(&cru->lock); > > + > > + return ret; > > +} > > + > > +static const struct v4l2_file_operations rzg2l_cru_fops = { > > + .owner = THIS_MODULE, > > + .unlocked_ioctl = video_ioctl2, > > + .open = rzg2l_cru_open, > > + .release = rzg2l_cru_release, > > + .poll = vb2_fop_poll, > > + .mmap = vb2_fop_mmap, > > + .read = vb2_fop_read, > > +}; > > + > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru) > > +{ > > + if (!video_is_registered(&cru->vdev)) > > + return; > > + > > + v4l2_info(&cru->v4l2_dev, "Removed %s\n", > > + video_device_node_name(&cru->vdev)); > > You can use dev_info(), but I'd actually use dev_dbg(). Same below. > OK, I will switch to dev_dbg(). > > + > > + /* Checks internally if vdev have been init or not */ > > + video_unregister_device(&cru->vdev); > > +} > > + > > +static void rzg2l_cru_notify(struct v4l2_subdev *sd, > > + unsigned int notification, void *arg) > > +{ > > + struct rzg2l_cru_dev *cru = > > + container_of(sd->v4l2_dev, struct rzg2l_cru_dev, v4l2_dev); > > + struct v4l2_subdev *remote; > > + struct media_pad *pad; > > + > > + pad = media_pad_remote_pad_first(&cru->pad); > > + if (!pad) > > + return; > > + > > + remote = media_entity_to_v4l2_subdev(pad->entity); > > + if (remote != sd) > > + return; > > + > > + switch (notification) { > > + case V4L2_DEVICE_NOTIFY_EVENT: > > + v4l2_event_queue(&cru->vdev, arg); > > + break; > > + } > > +} > > Drop this, userspace should listen for events on the subdevices that > generate them. > OK so in that case I can completely get rid of rzg2l_cru_notify(). > > + > > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru) > > +{ > > + struct video_device *vdev = &cru->vdev; > > + int ret; > > + > > + cru->v4l2_dev.notify = rzg2l_cru_notify; > > + > > + /* video node */ > > + vdev->v4l2_dev = &cru->v4l2_dev; > > + vdev->queue = &cru->queue; > > + snprintf(vdev->name, sizeof(vdev->name), "CRU output"); > > + vdev->release = video_device_release_empty; > > + vdev->lock = &cru->lock; > > + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | > > + V4L2_CAP_READWRITE; > > No read/write please. > OK, I will drop it. > > + > > + /* Set a default format */ > > + cru->format.pixelformat = RZG2L_CRU_DEFAULT_FORMAT; > > + cru->format.width = RZG2L_CRU_DEFAULT_WIDTH; > > + cru->format.height = RZG2L_CRU_DEFAULT_HEIGHT; > > + cru->format.field = RZG2L_CRU_DEFAULT_FIELD; > > + cru->format.colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; > > + > > + vdev->device_caps |= V4L2_CAP_IO_MC; > > + vdev->fops = &rzg2l_cru_fops; > > + vdev->ioctl_ops = &rzg2l_cru_ioctl_ops; > > + > > + rzg2l_cru_format_align(cru, &cru->format); > > I'd perform all this initialization in an init function called at probe > time, with only the actual registration left here, to be done at bound > time. > Agrreed, Ill create a rzg2l_cru_v4l2_init() for it. Cheers, Prabhakar
Hi Prabhakar, On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: ... > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > + struct rzg2l_cru_buffer, \ > + vb)->list) #define to_buf_list(vb2_buffer) \ (&container_of(vb2_buffer, struct rzg2l_cru_buffer, vb)->list) ... > +static int rzg2l_cru_open(struct file *file) > +{ > + struct rzg2l_cru_dev *cru = video_drvdata(file); > + int ret; > + > + ret = clk_prepare_enable(cru->pclk); > + if (ret) > + return ret; > + > + ret = clk_prepare_enable(cru->vclk); > + if (ret) > + goto disable_pclk; > + > + ret = clk_prepare_enable(cru->aclk); > + if (ret) > + goto disable_vclk; > + > + ret = mutex_lock_interruptible(&cru->lock); > + if (ret) > + goto disable_aclk; > + > + file->private_data = cru; > + ret = v4l2_fh_open(file); > + if (ret) > + goto err_unlock; > + > + ret = v4l2_pipeline_pm_get(&cru->vdev.entity); Please use runtime PM instead in sensor drivers, we're trying to get rid of this function. It'd be nice to have it in this one as well. > + if (ret < 0) > + goto err_open; > + > + mutex_unlock(&cru->lock); > + > + return 0; > +err_open: > + v4l2_fh_release(file); > +err_unlock: > + mutex_unlock(&cru->lock); > +disable_aclk: > + clk_disable_unprepare(cru->aclk); > +disable_vclk: > + clk_disable_unprepare(cru->vclk); > +disable_pclk: > + clk_disable_unprepare(cru->pclk); > + > + return ret; > +} ... > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru) > +{ > + if (!video_is_registered(&cru->vdev)) > + return; > + > + v4l2_info(&cru->v4l2_dev, "Removed %s\n", > + video_device_node_name(&cru->vdev)); I'd just leave this out. Same for the similar message on registration.
Hi Sakari, Thank you for the review. On Fri, Sep 23, 2022 at 9:14 PM Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > > Hi Prabhakar, > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > ... > > > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > > + struct rzg2l_cru_buffer, \ > > + vb)->list) > > #define to_buf_list(vb2_buffer) \ > (&container_of(vb2_buffer, struct rzg2l_cru_buffer, vb)->list) > OK. > > ... > > > +static int rzg2l_cru_open(struct file *file) > > +{ > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > + int ret; > > + > > + ret = clk_prepare_enable(cru->pclk); > > + if (ret) > > + return ret; > > + > > + ret = clk_prepare_enable(cru->vclk); > > + if (ret) > > + goto disable_pclk; > > + > > + ret = clk_prepare_enable(cru->aclk); > > + if (ret) > > + goto disable_vclk; > > + > > + ret = mutex_lock_interruptible(&cru->lock); > > + if (ret) > > + goto disable_aclk; > > + > > + file->private_data = cru; > > + ret = v4l2_fh_open(file); > > + if (ret) > > + goto err_unlock; > > + > > + ret = v4l2_pipeline_pm_get(&cru->vdev.entity); > > Please use runtime PM instead in sensor drivers, we're trying to get rid of > this function. > OK. > It'd be nice to have it in this one as well. > I'll will switch to runtime PM. > > + if (ret < 0) > > + goto err_open; > > + > > + mutex_unlock(&cru->lock); > > + > > + return 0; > > +err_open: > > + v4l2_fh_release(file); > > +err_unlock: > > + mutex_unlock(&cru->lock); > > +disable_aclk: > > + clk_disable_unprepare(cru->aclk); > > +disable_vclk: > > + clk_disable_unprepare(cru->vclk); > > +disable_pclk: > > + clk_disable_unprepare(cru->pclk); > > + > > + return ret; > > +} > > ... > > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru) > > +{ > > + if (!video_is_registered(&cru->vdev)) > > + return; > > + > > + v4l2_info(&cru->v4l2_dev, "Removed %s\n", > > + video_device_node_name(&cru->vdev)); > > I'd just leave this out. Same for the similar message on registration. > OK, I'll drop both the messages. Cheers, Prabhakar
Hi Prabhakar, On Fri, Sep 23, 2022 at 08:02:12PM +0100, Lad, Prabhakar wrote: > On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart wrote: > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > > > Based on a patch in the BSP by Hien Huynh > > > <hien.huynh.px@renesas.com> > > > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > --- > > > v1 -> v2 > > > * No change > > > > > > RFC v2 -> v1 > > > * Moved the driver to renesas folder > > > * Fixed review comments pointed by Jacopo > > > > > > RFC v1 -> RFC v2 > > > * Dropped group > > > * Dropped CSI subdev and implemented as new driver > > > * Dropped "mc_" from function names > > > * Moved the driver to renesas folder > > > --- > > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > > > I'd merge those two files together, they both handle the video node. > > There's a comment below that recommends adding a subdev, that should > > then go to a separate file. > > OK, I'll merge these files into rzg2l-video.c. > > > > 6 files changed, 1669 insertions(+) > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > index 57c40bb499df..08ff0e96b3f5 100644 > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > > > To compile this driver as a module, choose M here: the > > > module will be called rzg2l-csi2. > > > + > > > +config VIDEO_RZG2L_CRU > > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > + depends on V4L_PLATFORM_DRIVERS > > > + depends on VIDEO_DEV && OF > > > + select MEDIA_CONTROLLER > > > + select V4L2_FWNODE > > > + select VIDEOBUF2_DMA_CONTIG > > > + select VIDEO_RZG2L_CSI2 > > > > Is this required, can't the CRU be used with a parallel sensor without > > the CSI-2 receiver ? > > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > > > + select VIDEO_V4L2_SUBDEV_API > > > + help > > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > > + Unit (CRU) driver. > > > + > > > + To compile this driver as a module, choose M here: the > > > + module will be called rzg2l-cru. > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > index 91ea97a944e6..7628809e953f 100644 > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > @@ -1,3 +1,6 @@ > > > # SPDX-License-Identifier: GPL-2.0 > > > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > > + > > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > new file mode 100644 > > > index 000000000000..b5d4110b1913 > > > --- /dev/null > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > @@ -0,0 +1,395 @@ > > > +// SPDX-License-Identifier: GPL-2.0+ > > > +/* > > > + * Driver for Renesas RZ/G2L CRU > > > + * > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > + * > > > + * Based on Renesas R-Car VIN > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > + * Copyright (C) 2008 Magnus Damm > > > + */ > > > + > > > +#include <linux/clk.h> > > > +#include <linux/module.h> > > > +#include <linux/mod_devicetable.h> > > > +#include <linux/of.h> > > > +#include <linux/of_device.h> > > > +#include <linux/of_graph.h> > > > +#include <linux/platform_device.h> > > > +#include <linux/pm_runtime.h> > > > + > > > +#include <media/v4l2-fwnode.h> > > > +#include <media/v4l2-mc.h> > > > + > > > +#include "rzg2l-cru.h" > > > + > > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > > > As this macro is only used to get the rzg2l_cru_dev pointer from the > > v4l2_async_notifier pointer, you can replace it with > > > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > > > I would also turn it into a static inline function for additional > > compile-time type safety. > > OK, I will do it as mentioned above. > > > > + > > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > > + unsigned int notification) > > > +{ > > > + struct media_entity *entity; > > > + struct rzg2l_cru_dev *cru; > > > + struct media_pad *csi_pad; > > > + struct v4l2_subdev *sd; > > > + int ret; > > > + > > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > > + if (ret) > > > + return ret; > > > + > > > + /* Only care about link enablement for CRU nodes. */ > > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > > + return 0; > > > + > > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > > + /* > > > + * Don't allow link changes if any entity in the graph is > > > + * streaming, modifying the CHSEL register fields can disrupt > > > + * running streams. > > > + */ > > > + media_device_for_each_entity(entity, &cru->mdev) > > > + if (media_entity_is_streaming(entity)) > > > + return -EBUSY; > > > + > > > + mutex_lock(&cru->mdev_lock); > > > + > > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > > + if (csi_pad) { > > > + ret = -EMLINK; > > > + goto out; > > > + } > > > + > > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > > + if (cru->csi.subdev == sd) { > > > + cru->csi.channel = link->source->index - 1; > > > + cru->is_csi = true; > > > + } else { > > > + ret = -ENODEV; > > > + } > > > + > > > +out: > > > + mutex_unlock(&cru->mdev_lock); > > > + > > > + return ret; > > > +} > > > + > > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > > + .link_notify = rzg2l_cru_csi2_link_notify, > > > +}; > > > + > > > +/* ----------------------------------------------------------------------------- > > > + * Group async notifier > > > + */ > > > + > > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > > +{ > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > + unsigned int i; > > > + int ret; > > > + > > > + ret = media_device_register(&cru->mdev); > > > + if (ret) > > > + return ret; > > > > I'd move the v4l2_device_register() call here, as it's the V4L2 > > counterpart of the media device, and handling them together would be > > best. > > OK. > > > > + > > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > > + if (ret) { > > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > > + return ret; > > > + } > > > + > > > + if (!video_is_registered(&cru->vdev)) { > > > > Can this happen ? > > No, I'll drop this check. > > > > + ret = rzg2l_cru_v4l2_register(cru); > > > + if (ret) > > > + return ret; > > > + } > > > + > > > + /* Create all media device links between CRU and CSI-2's. */ > > > + /* > > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > > + * should be implemented by streams API which is under development > > > + * so for now just link it to VC0 > > > + */ > > > > The streams API won't require more links, so I'd drop the comment and > > the loop and create a single link. > > OK. > > > > + for (i = 1; i <= 1; i++) { > > > + struct media_entity *source, *sink; > > > + > > > + source = &cru->csi.subdev->entity; > > > + sink = &cru->vdev.entity; > > > > Hmmm... I'd recommend adding a subdev to model the image processing > > pipeline of the CRU, between the CSI-2 receiver and the video node. That > > will help when you'll add support for parallel sensors, and it will also > > be needed by the streams API to select which virtual channel to capture. > > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? I think MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER would be more appropriate. > > > + > > > + ret = media_create_pad_link(source, i, sink, 0, 0); > > > + if (ret) { > > > + dev_err(cru->dev, "Error adding link from %s to %s\n", > > > + source->name, sink->name); > > > + break; > > > + } > > > + } > > > + > > > + return ret; > > > +} > > > + > > > +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, > > > + struct v4l2_subdev *subdev, > > > + struct v4l2_async_subdev *asd) > > > +{ > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > + > > > + rzg2l_cru_v4l2_unregister(cru); > > > + > > > + mutex_lock(&cru->mdev_lock); > > > + > > > + if (cru->csi.asd == asd) { > > > + cru->csi.subdev = NULL; > > > + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); > > > + } > > > + > > > + mutex_unlock(&cru->mdev_lock); > > > + > > > + media_device_unregister(&cru->mdev); > > > +} > > > + > > > +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, > > > + struct v4l2_subdev *subdev, > > > + struct v4l2_async_subdev *asd) > > > +{ > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > + > > > + mutex_lock(&cru->mdev_lock); > > > + > > > + if (cru->csi.asd == asd) { > > > + cru->csi.subdev = subdev; > > > + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); > > > + } > > > + > > > + mutex_unlock(&cru->mdev_lock); > > > + > > > + return 0; > > > +} > > > + > > > +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { > > > + .bound = rzg2l_cru_group_notify_bound, > > > + .unbind = rzg2l_cru_group_notify_unbind, > > > + .complete = rzg2l_cru_group_notify_complete, > > > +}; > > > + > > > +static int rvin_mc_parse_of(struct rzg2l_cru_dev *cru, unsigned int id) > > > > The id parameter is always 0, I'd drop it. > > Agreed, I will drop it. > > > > +{ > > > + struct v4l2_fwnode_endpoint vep = { > > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > > + }; > > > + struct fwnode_handle *ep, *fwnode; > > > + struct v4l2_async_subdev *asd; > > > + int ret; > > > + > > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, id, 0); > > > + if (!ep) > > > + return 0; > > > + > > > + fwnode = fwnode_graph_get_remote_endpoint(ep); > > > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > > > + fwnode_handle_put(ep); > > > + if (ret) { > > > + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); > > > + ret = -EINVAL; > > > + goto out; > > > + } > > > + > > > + if (!of_device_is_available(to_of_node(fwnode))) { > > > + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", > > > + to_of_node(fwnode)); > > > + ret = -ENOTCONN; > > > + goto out; > > > + } > > > + > > > + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, > > > + struct v4l2_async_subdev); > > > + if (IS_ERR(asd)) { > > > + ret = PTR_ERR(asd); > > > + goto out; > > > + } > > > + > > > + cru->csi.asd = asd; > > > + > > > + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", > > > + to_of_node(fwnode), vep.base.id); > > > +out: > > > + fwnode_handle_put(fwnode); > > > + > > > + return ret; > > > +} > > > + > > > +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) > > > +{ > > > + int ret; > > > + > > > + v4l2_async_nf_init(&cru->notifier); > > > + > > > + ret = rvin_mc_parse_of(cru, 0); > > > + if (ret) > > > + return ret; > > > + > > > + cru->notifier.ops = &rzg2l_cru_async_ops; > > > + > > > + if (list_empty(&cru->notifier.asd_list)) > > > + return 0; > > > + > > > + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); > > > + if (ret < 0) { > > > + dev_err(cru->dev, "Notifier registration failed\n"); > > > + v4l2_async_nf_cleanup(&cru->notifier); > > > + return ret; > > > + } > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_csi2_init(struct rzg2l_cru_dev *cru) > > > > The naming is a bit weird, as this isn't related to CSI-2. I would name > > the function rzg2l_cru_media_init(). > > Agreed, I will rename it. > > > > +{ > > > + struct media_device *mdev = NULL; > > > + const struct of_device_id *match; > > > + int ret; > > > + > > > + cru->pad.flags = MEDIA_PAD_FL_SINK; > > > + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); > > > + if (ret) > > > + return ret; > > > + > > > + mutex_init(&cru->mdev_lock); > > > + mdev = &cru->mdev; > > > + mdev->dev = cru->dev; > > > + mdev->ops = &rzg2l_cru_media_ops; > > > + > > > + match = of_match_node(cru->dev->driver->of_match_table, > > > + cru->dev->of_node); > > > + > > > + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); > > > + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); > > > + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", > > > + dev_name(mdev->dev)); > > > + > > > + cru->v4l2_dev.mdev = &cru->mdev; > > > + > > > + media_device_init(mdev); > > > + > > > + ret = rzg2l_cru_mc_parse_of_graph(cru); > > > + if (ret) { > > > + mutex_lock(&cru->mdev_lock); > > > + cru->v4l2_dev.mdev = NULL; > > > + mutex_unlock(&cru->mdev_lock); > > > + } > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_probe(struct platform_device *pdev) > > > +{ > > > + struct rzg2l_cru_dev *cru; > > > + int irq, ret; > > > + > > > + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); > > > + if (!cru) > > > + return -ENOMEM; > > > + > > > + cru->base = devm_platform_ioremap_resource(pdev, 0); > > > + if (IS_ERR(cru->base)) > > > + return PTR_ERR(cru->base); > > > + > > > + cru->presetn = devm_reset_control_get(&pdev->dev, "presetn"); > > > + if (IS_ERR(cru->presetn)) > > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), > > > + "failed to get cpg presetn\n"); > > > + > > > + cru->aresetn = devm_reset_control_get(&pdev->dev, "aresetn"); > > > + if (IS_ERR(cru->aresetn)) > > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), > > > + "failed to get cpg aresetn\n"); > > > + > > > + cru->vclk = devm_clk_get(&pdev->dev, "vclk"); > > > + if (IS_ERR(cru->vclk)) { > > > + dev_err(&pdev->dev, "Failed to get vclk"); > > > + return PTR_ERR(cru->vclk); > > > > You could use dev_err_probe() here too (as well as below). > > OK. > > > > + } > > > + > > > + cru->pclk = devm_clk_get(&pdev->dev, "pclk"); > > > + if (IS_ERR(cru->pclk)) { > > > + dev_err(&pdev->dev, "Failed to get pclk"); > > > + return PTR_ERR(cru->pclk); > > > + } > > > + > > > + cru->aclk = devm_clk_get(&pdev->dev, "aclk"); > > > + if (IS_ERR(cru->aclk)) { > > > + dev_err(&pdev->dev, "Failed to get aclk"); > > > + return PTR_ERR(cru->aclk); > > > + } > > > + > > > + cru->dev = &pdev->dev; > > > + cru->info = of_device_get_match_data(&pdev->dev); > > > + > > > + irq = platform_get_irq(pdev, 0); > > > + if (irq < 0) > > > + return irq; > > > + > > > + ret = rzg2l_cru_dma_register(cru, irq); > > > + if (ret) > > > + return ret; > > > + > > > + platform_set_drvdata(pdev, cru); > > > + > > > + ret = rzg2l_cru_csi2_init(cru); > > > + if (ret) > > > + goto error_dma_unregister; > > > + > > > + cru->num_buf = HW_BUFFER_DEFAULT; > > > + pm_suspend_ignore_children(&pdev->dev, true); > > > + pm_runtime_enable(&pdev->dev); > > > + > > > + return 0; > > > + > > > +error_dma_unregister: > > > + rzg2l_cru_dma_unregister(cru); > > > + > > > + return ret; > > > +} > > > + > > > +static const struct of_device_id rzg2l_cru_of_id_table[] = { > > > + { > > > + .compatible = "renesas,rzg2l-cru", > > > + }, > > > + { /* sentinel */ } > > > +}; > > > +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); > > > + > > > +static int rzg2l_cru_remove(struct platform_device *pdev) > > > +{ > > > + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); > > > + > > > + pm_runtime_disable(&pdev->dev); > > > + > > > + rzg2l_cru_v4l2_unregister(cru); > > > + > > > + v4l2_async_nf_unregister(&cru->notifier); > > > + v4l2_async_nf_cleanup(&cru->notifier); > > > + > > > + media_device_cleanup(&cru->mdev); > > > + mutex_destroy(&cru->mdev_lock); > > > + cru->v4l2_dev.mdev = NULL; > > > > Is this needed ? > > Not required. > > > > + > > > + rzg2l_cru_dma_unregister(cru); > > > + > > > + return 0; > > > +} > > > + > > > +static struct platform_driver rzg2l_cru_driver = { > > > + .driver = { > > > + .name = "rzg2l-cru", > > > + .of_match_table = rzg2l_cru_of_id_table, > > > + }, > > > + .probe = rzg2l_cru_probe, > > > + .remove = rzg2l_cru_remove, > > > > No PM ? > > I plan to gradually add at a later point. > > > > +}; > > > + > > > +module_platform_driver(rzg2l_cru_driver); > > > + > > > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > > > +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); > > > +MODULE_LICENSE("GPL"); > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > new file mode 100644 > > > index 000000000000..a834680a3200 > > > --- /dev/null > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > @@ -0,0 +1,152 @@ > > > +/* SPDX-License-Identifier: GPL-2.0+ */ > > > +/* > > > + * Driver for Renesas RZ/G2L CRU > > > + * > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > + * > > > > Extra blank line. > > Oops, I will drop it. > > > > + */ > > > + > > > +#ifndef __RZG2L_CRU__ > > > +#define __RZG2L_CRU__ > > > + > > > +#include <linux/reset.h> > > > + > > > +#include <media/v4l2-async.h> > > > +#include <media/v4l2-ctrls.h> > > > +#include <media/v4l2-dev.h> > > > +#include <media/v4l2-device.h> > > > +#include <media/videobuf2-v4l2.h> > > > + > > > +/* Number of HW buffers */ > > > +#define HW_BUFFER_MAX 8 > > > +#define HW_BUFFER_DEFAULT 3 > > > > Could you prefix macro names with CRU_ (or RZG2L_CRU_, up to you) ? > > These names are a bit generic and could lead to clashes. > > Agreed, I will rename it. > > > > + > > > +/* Address alignment mask for HW buffers */ > > > +#define HW_BUFFER_MASK 0x1ff > > > + > > > +/* Maximum number of CSI2 virtual channels */ > > > +#define CSI2_VCHANNEL 4 > > > + > > > +#define CRU_MAX_INPUT_WIDTH 2800 > > > +#define CRU_MAX_INPUT_HEIGHT 4095 > > > + > > > +/** > > > + * enum rzg2l_cru_dma_state - DMA states > > > + * @RZG2L_CRU_DMA_STOPPED: No operation in progress > > > + * @RZG2L_CRU_DMA_STARTING: Capture starting up > > > + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers > > > + * @RZG2L_CRU_DMA_STOPPING: Stopping operation > > > + */ > > > +enum rzg2l_cru_dma_state { > > > + RZG2L_CRU_DMA_STOPPED = 0, > > > + RZG2L_CRU_DMA_STARTING, > > > + RZG2L_CRU_DMA_RUNNING, > > > + RZG2L_CRU_DMA_STOPPING, > > > +}; > > > + > > > +struct rzg2l_cru_csi { > > > + struct v4l2_async_subdev *asd; > > > + struct v4l2_subdev *subdev; > > > + u32 channel; > > > +}; > > > + > > > +/** > > > + * struct rzg2l_cru_dev - Renesas CRU device structure > > > + * @dev: (OF) device > > > + * @base: device I/O register space remapped to virtual memory > > > + * @info: info about CRU instance > > > + * > > > + * @presetn: CRU_PRESETN reset line > > > + * @aresetn: CRU_ARESETN reset line > > > + * > > > + * @vclk: CRU Main clock > > > + * @pclk: CPU Register access clock > > > + * @aclk: CRU image transfer clock > > > + * > > > + * @vdev: V4L2 video device associated with CRU > > > + * @v4l2_dev: V4L2 device > > > + * @ctrl_handler: V4L2 control handler > > > + * @num_buf: Holds the current number of buffers enabled > > > + * @notifier: V4L2 asynchronous subdevs notifier > > > + * > > > + * @csi: CSI info > > > + * @mdev: media device > > > + * @mdev_lock: protects the count, notifier and csi members > > > + * @pad: media pad for the video device entity > > > + * > > > + * @lock: protects @queue > > > + * @queue: vb2 buffers queue > > > + * @scratch: cpu address for scratch buffer > > > + * @scratch_phys: physical address of the scratch buffer > > > + * > > > + * @qlock: protects @queue_buf, @buf_list, @sequence > > > + * @state > > > + * @queue_buf: Keeps track of buffers given to HW slot > > > + * @buf_list: list of queued buffers > > > + * @sequence: V4L2 buffers sequence number > > > + * @state: keeps track of operation state > > > + * > > > + * @is_csi: flag to mark the CRU as using a CSI-2 subdevice > > > + * > > > + * @input_is_yuv: flag to mark the input format of CRU > > > + * @output_is_yuv: flag to mark the output format of CRU > > > + * > > > + * @mbus_code: media bus format code > > > + * @format: active V4L2 pixel format > > > + * > > > + * @compose: active composing > > > + */ > > > +struct rzg2l_cru_dev { > > > + struct device *dev; > > > + void __iomem *base; > > > + const struct rzg2l_cru_info *info; > > > + > > > + struct reset_control *presetn; > > > + struct reset_control *aresetn; > > > + > > > + struct clk *vclk; > > > + struct clk *pclk; > > > + struct clk *aclk; > > > + > > > + struct video_device vdev; > > > + struct v4l2_device v4l2_dev; > > > + u8 num_buf; > > > + > > > + struct v4l2_async_notifier notifier; > > > + > > > + struct rzg2l_cru_csi csi; > > > + struct media_device mdev; > > > + struct mutex mdev_lock; > > > + struct media_pad pad; > > > + > > > + struct mutex lock; > > > + struct vb2_queue queue; > > > + void *scratch; > > > + dma_addr_t scratch_phys; > > > + > > > + spinlock_t qlock; > > > + struct vb2_v4l2_buffer *queue_buf[HW_BUFFER_MAX]; > > > + struct list_head buf_list; > > > + unsigned int sequence; > > > + enum rzg2l_cru_dma_state state; > > > + > > > + bool is_csi; > > > + > > > + bool input_is_yuv; > > > + bool output_is_yuv; > > > + > > > + u32 mbus_code; > > > + struct v4l2_pix_format format; > > > + > > > + struct v4l2_rect compose; > > > +}; > > > + > > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq); > > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); > > > + > > > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru); > > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru); > > > + > > > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); > > > + > > > +#endif > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > new file mode 100644 > > > index 000000000000..44efd071f562 > > > --- /dev/null > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > @@ -0,0 +1,734 @@ > > > +// SPDX-License-Identifier: GPL-2.0+ > > > +/* > > > + * Driver for Renesas RZ/G2L CRU > > > + * > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > + * > > > + * Based on Renesas R-Car VIN > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > + * Copyright (C) 2008 Magnus Damm > > > + */ > > > + > > > +#include <linux/clk.h> > > > +#include <linux/delay.h> > > > +#include <linux/interrupt.h> > > > + > > > +#include <media/videobuf2-dma-contig.h> > > > + > > > +#include "rzg2l-cru.h" > > > +#include "rzg2l-csi2.h" > > > + > > > +/* HW CRU Registers Definition */ > > > +/* CRU Control Register */ > > > +#define CRUnCTRL 0x0 > > > +#define CRUnCTRL_VINSEL(x) ((x) << 0) > > > + > > > +/* CRU Interrupt Enable Register */ > > > +#define CRUnIE 0x4 > > > +#define CRUnIE_SFE BIT(16) > > > +#define CRUnIE_EFE BIT(17) > > > + > > > +/* CRU Interrupt Status Register */ > > > +#define CRUnINTS 0x8 > > > +#define CRUnINTS_SFS BIT(16) > > > + > > > +/* CRU Reset Register */ > > > +#define CRUnRST 0xc > > > +#define CRUnRST_VRESETN BIT(0) > > > + > > > +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ > > > +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) > > > + > > > +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ > > > +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) > > > + > > > +/* Memory Bank Enable Register for CRU Image Data */ > > > +#define AMnMBVALID 0x148 > > > +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) > > > + > > > +/* Memory Bank Status Register for CRU Image Data */ > > > +#define AMnMBS 0x14c > > > +#define AMnMBS_MBSTS 0x7 > > > + > > > +/* AXI Master FIFO Pointer Register for CRU Image Data */ > > > +#define AMnFIFOPNTR 0x168 > > > +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) > > > +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) > > > + > > > +/* AXI Master Transfer Stop Register for CRU Image Data */ > > > +#define AMnAXISTP 0x174 > > > +#define AMnAXISTP_AXI_STOP BIT(0) > > > + > > > +/* AXI Master Transfer Stop Status Register for CRU Image Data */ > > > +#define AMnAXISTPACK 0x178 > > > +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) > > > + > > > +/* CRU Image Processing Enable Register */ > > > +#define ICnEN 0x200 > > > +#define ICnEN_ICEN BIT(0) > > > + > > > +/* CRU Image Processing Main Control Register */ > > > +#define ICnMC 0x208 > > > +#define ICnMC_CSCTHR BIT(5) > > > +#define ICnMC_INF_YUV8_422 (0x1e << 16) > > > +#define ICnMC_INF_USER (0x30 << 16) > > > +#define ICnMC_VCSEL(x) ((x) << 22) > > > +#define ICnMC_INF_MASK GENMASK(21, 16) > > > + > > > +/* CRU Module Status Register */ > > > +#define ICnMS 0x254 > > > +#define ICnMS_IA BIT(2) > > > + > > > +/* CRU Data Output Mode Register */ > > > +#define ICnDMR 0x26c > > > +#define ICnDMR_YCMODE_UYVY (1 << 4) > > > + > > > +#define RZG2L_TIMEOUT_MS 100 > > > +#define RZG2L_RETRIES 10 > > > + > > > +struct rzg2l_cru_buffer { > > > + struct vb2_v4l2_buffer vb; > > > + struct list_head list; > > > +}; > > > + > > > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > > > + struct rzg2l_cru_buffer, \ > > > + vb)->list) > > > + > > > +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) > > > +{ > > > + iowrite32(value, cru->base + offset); > > > +} > > > + > > > +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) > > > +{ > > > + return ioread32(cru->base + offset); > > > +} > > > + > > > +/* Need to hold qlock before calling */ > > > +static void return_unused_buffers(struct rzg2l_cru_dev *cru, > > > + enum vb2_buffer_state state) > > > +{ > > > + struct rzg2l_cru_buffer *buf, *node; > > > + unsigned long flags; > > > + unsigned int i; > > > + > > > + spin_lock_irqsave(&cru->qlock, flags); > > > + for (i = 0; i < cru->num_buf; i++) { > > > + if (cru->queue_buf[i]) { > > > + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, > > > + state); > > > + cru->queue_buf[i] = NULL; > > > + } > > > + } > > > + > > > + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { > > > + vb2_buffer_done(&buf->vb.vb2_buf, state); > > > + list_del(&buf->list); > > > + } > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > +} > > > + > > > +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, > > > + unsigned int *nplanes, unsigned int sizes[], > > > + struct device *alloc_devs[]) > > > +{ > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > + > > > + /* Make sure the image size is large enough. */ > > > + if (*nplanes) > > > + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; > > > + > > > + *nplanes = 1; > > > + sizes[0] = cru->format.sizeimage; > > > + > > > + return 0; > > > +}; > > > + > > > +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) > > > +{ > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > > + unsigned long size = cru->format.sizeimage; > > > + > > > + if (vb2_plane_size(vb, 0) < size) { > > > + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", > > > + vb2_plane_size(vb, 0), size); > > > + return -EINVAL; > > > + } > > > + > > > + vb2_set_plane_payload(vb, 0, size); > > > + > > > + return 0; > > > +} > > > + > > > +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) > > > +{ > > > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > > + unsigned long flags; > > > + > > > + spin_lock_irqsave(&cru->qlock, flags); > > > + > > > + list_add_tail(to_buf_list(vbuf), &cru->buf_list); > > > + > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > +} > > > + > > > +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, > > > + struct v4l2_subdev *sd, > > > + struct media_pad *pad) > > > +{ > > > + struct v4l2_subdev_format fmt = { > > > + .which = V4L2_SUBDEV_FORMAT_ACTIVE, > > > + }; > > > + > > > + fmt.pad = pad->index; > > > + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) > > > + return -EPIPE; > > > + > > > + if (cru->is_csi) { > > > + switch (fmt.format.code) { > > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > > + break; > > > + default: > > > + return -EPIPE; > > > + } > > > + } > > > + cru->mbus_code = fmt.format.code; > > > + > > > + switch (fmt.format.field) { > > > + case V4L2_FIELD_TOP: > > > + case V4L2_FIELD_BOTTOM: > > > + case V4L2_FIELD_NONE: > > > + case V4L2_FIELD_INTERLACED_TB: > > > + case V4L2_FIELD_INTERLACED_BT: > > > + case V4L2_FIELD_INTERLACED: > > > + case V4L2_FIELD_SEQ_TB: > > > + case V4L2_FIELD_SEQ_BT: > > > + break; > > > + default: > > > + return -EPIPE; > > > + } > > > + > > > + if (fmt.format.width != cru->format.width || > > > + fmt.format.height != cru->format.height || > > > + fmt.format.code != cru->mbus_code) > > > + return -EPIPE; > > > + > > > + return 0; > > > +} > > > + > > > +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, > > > + int slot, dma_addr_t addr) > > > +{ > > > + const struct v4l2_format_info *fmt; > > > + int offsetx, offsety; > > > + dma_addr_t offset; > > > + > > > + fmt = rzg2l_cru_format_from_pixel(cru->format.pixelformat); > > > + > > > + /* > > > + * There is no HW support for composition do the best we can > > > + * by modifying the buffer offset > > > + */ > > > + offsetx = cru->compose.left * fmt->bpp[0]; > > > + offsety = cru->compose.top * cru->format.bytesperline; > > > + offset = addr + offsetx + offsety; > > > + > > > + /* > > > + * The address needs to be 512 bytes aligned. Driver should never accept > > > + * settings that do not satisfy this in the first place... > > > + */ > > > + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) > > > + return; > > > + > > > + /* Currently, we just use the buffer in 32 bits address */ > > > + rzg2l_cru_write(cru, AMnMBxADDRL(slot), offset); > > > + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); > > > +} > > > + > > > +/* > > > + * Moves a buffer from the queue to the HW slot. If no buffer is > > > + * available use the scratch buffer. The scratch buffer is never > > > + * returned to userspace, its only function is to enable the capture > > > + * loop to keep running. > > > + */ > > > +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) > > > +{ > > > + struct vb2_v4l2_buffer *vbuf; > > > + struct rzg2l_cru_buffer *buf; > > > + dma_addr_t phys_addr; > > > + > > > + /* A already populated slot shall never be overwritten. */ > > > + if (WARN_ON(cru->queue_buf[slot])) > > > + return; > > > + > > > + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); > > > + > > > + if (list_empty(&cru->buf_list)) { > > > + cru->queue_buf[slot] = NULL; > > > + phys_addr = cru->scratch_phys; > > > + } else { > > > + /* Keep track of buffer we give to HW */ > > > + buf = list_entry(cru->buf_list.next, > > > + struct rzg2l_cru_buffer, list); > > > + vbuf = &buf->vb; > > > + list_del_init(to_buf_list(vbuf)); > > > + cru->queue_buf[slot] = vbuf; > > > + > > > + /* Setup DMA */ > > > + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); > > > + } > > > + > > > + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); > > > +} > > > + > > > +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) > > > +{ > > > + unsigned int slot; > > > + > > > + /* > > > + * Set image data memory banks. > > > + * Currently, we will use maximum address. > > > + */ > > > + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); > > > + > > > + for (slot = 0; slot < cru->num_buf; slot++) > > > + rzg2l_cru_fill_hw_slot(cru, slot); > > > +} > > > + > > > +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru) > > > +{ > > > + u32 icnmc; > > > + > > > + switch (cru->mbus_code) { > > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > > + icnmc = ICnMC_INF_YUV8_422; > > > + cru->input_is_yuv = true; > > > + break; > > > + default: > > > + cru->input_is_yuv = false; > > > + icnmc = ICnMC_INF_USER; > > > + break; > > > + } > > > + > > > + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); > > > + > > > + /* Set virtual channel CSI2 */ > > > + icnmc |= ICnMC_VCSEL(cru->csi.channel); > > > + > > > + rzg2l_cru_write(cru, ICnMC, icnmc); > > > +} > > > + > > > +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru) > > > +{ > > > + u32 icndmr; > > > + > > > + if (cru->is_csi) > > > + rzg2l_cru_csi2_setup(cru); > > > + > > > + /* Output format */ > > > + switch (cru->format.pixelformat) { > > > + case V4L2_PIX_FMT_UYVY: > > > + icndmr = ICnDMR_YCMODE_UYVY; > > > + cru->output_is_yuv = true; > > > + break; > > > + default: > > > + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", > > > + cru->format.pixelformat); > > > + return -EINVAL; > > > + } > > > + > > > + /* If input and output use same colorspace, do bypass mode */ > > > + if (cru->output_is_yuv == cru->input_is_yuv) > > > + rzg2l_cru_write(cru, ICnMC, > > > + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); > > > + else > > > + rzg2l_cru_write(cru, ICnMC, > > > + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); > > > + > > > + /* Set output data format */ > > > + rzg2l_cru_write(cru, ICnDMR, icndmr); > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) > > > +{ > > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > > + struct media_pipeline *pipe; > > > + struct v4l2_subdev *sd; > > > + struct media_pad *pad; > > > + unsigned long flags; > > > + int ret; > > > + > > > + pad = media_pad_remote_pad_first(&cru->pad); > > > + if (!pad) > > > + return -EPIPE; > > > + > > > + sd = media_entity_to_v4l2_subdev(pad->entity); > > > + > > > + if (!on) { > > > + media_pipeline_stop(&cru->vdev.entity); > > > + return v4l2_subdev_call(sd, video, s_stream, 0); > > > + } > > > + > > > + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); > > > + if (ret) > > > + return ret; > > > + > > > + rzg2l_csi2_dphy_setting(csi2, 1); > > > + > > > + spin_lock_irqsave(&cru->qlock, flags); > > > + > > > + /* Select a video input */ > > > + if (cru->is_csi) > > > + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); > > > + > > > + /* Cancel the software reset for image processing block */ > > > + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); > > > + > > > + /* Disable and clear the interrupt before using */ > > > + rzg2l_cru_write(cru, CRUnIE, 0); > > > + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); > > > + > > > + /* Initialize the AXI master */ > > > + rzg2l_cru_initialize_axi(cru); > > > + > > > + /* Initialize image convert */ > > > + ret = rzg2l_cru_initialize_image_conv(cru); > > > + if (ret) { > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > + return ret; > > > + } > > > + > > > + /* Enable interrupt */ > > > + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); > > > + > > > + /* Enable image processing reception */ > > > + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); > > > + > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > + > > > + pipe = sd->entity.pipe ? sd->entity.pipe : &cru->vdev.pipe; > > > + ret = media_pipeline_start(&cru->vdev.entity, pipe); > > > + if (ret) > > > + return ret; > > > + > > > + clk_disable_unprepare(cru->vclk); > > > + > > > + rzg2l_csi2_mipi_link_setting(csi2, 1); > > > + > > > + ret = clk_prepare_enable(cru->vclk); > > > + if (ret) > > > + return ret; > > > + > > > + rzg2l_csi2_cmn_rstb_deassert(csi2); > > > + > > > + ret = v4l2_subdev_call(sd, video, s_stream, 1); > > > + if (ret == -ENOIOCTLCMD) > > > + ret = 0; > > > + if (ret) > > > + media_pipeline_stop(&cru->vdev.entity); > > > + > > > + return ret; > > > +} > > > + > > > +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) > > > +{ > > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > > + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; > > > + unsigned int retries = 0; > > > + unsigned long flags; > > > + u32 icnms; > > > + > > > + cru->state = RZG2L_CRU_DMA_STOPPING; > > > + > > > + rzg2l_cru_set_stream(cru, 0); > > > + > > > + rzg2l_csi2_dphy_setting(csi2, 0); > > > + > > > + rzg2l_csi2_mipi_link_setting(csi2, 0); > > > + > > > + spin_lock_irqsave(&cru->qlock, flags); > > > + > > > + /* Disable and clear the interrupt */ > > > + rzg2l_cru_write(cru, CRUnIE, 0); > > > + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); > > > + > > > + /* Stop the operation of image conversion */ > > > + rzg2l_cru_write(cru, ICnEN, 0); > > > + > > > + /* Wait for streaming to stop */ > > > + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > + msleep(RZG2L_TIMEOUT_MS); > > > + spin_lock_irqsave(&cru->qlock, flags); > > > + } > > > + > > > + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; > > > + if (icnms) > > > + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); > > > + > > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > > + > > > + /* Wait until the FIFO becomes empty */ > > > + for (retries = 5; retries > 0; retries--) { > > > + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); > > > + > > > + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; > > > + amnfifopntr_r_y = > > > + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; > > > + if (amnfifopntr_w == amnfifopntr_r_y) > > > + break; > > > + > > > + usleep_range(10, 20); > > > + } > > > + > > > + /* Notify that FIFO is not empty here */ > > > + if (!retries) > > > + dev_err(cru->dev, "Failed to empty FIFO\n"); > > > + > > > + /* Stop AXI bus */ > > > + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); > > > + > > > + /* Wait until the AXI bus stop */ > > > + for (retries = 5; retries > 0; retries--) { > > > + if (rzg2l_cru_read(cru, AMnAXISTPACK) & > > > + AMnAXISTPACK_AXI_STOP_ACK) > > > + break; > > > + > > > + usleep_range(10, 20); > > > + }; > > > + > > > + /* Notify that AXI bus can not stop here */ > > > + if (!retries) > > > + dev_err(cru->dev, "Failed to stop AXI bus\n"); > > > + > > > + /* Cancel the AXI bus stop request */ > > > + rzg2l_cru_write(cru, AMnAXISTP, 0); > > > + > > > + /* Resets the image processing module */ > > > + rzg2l_cru_write(cru, CRUnRST, 0); > > > + > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > + > > > + /* Set reset state */ > > > + reset_control_assert(cru->aresetn); > > > +} > > > + > > > +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) > > > +{ > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > + int ret; > > > + > > > + /* Release reset state */ > > > + ret = reset_control_deassert(cru->aresetn); > > > + if (ret) { > > > + dev_err(cru->dev, "failed to deassert aresetn\n"); > > > + return ret; > > > + } > > > + > > > + /* Allocate scratch buffer. */ > > > + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, > > > + &cru->scratch_phys, GFP_KERNEL); > > > + if (!cru->scratch) { > > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > > + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); > > > + return -ENOMEM; > > > + } > > > + > > > + cru->sequence = 0; > > > + > > > + ret = rzg2l_cru_set_stream(cru, 1); > > > + if (ret) { > > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > > + goto out; > > > + } > > > + > > > + cru->state = RZG2L_CRU_DMA_STARTING; > > > + > > > + dev_dbg(cru->dev, "Starting to capture\n"); > > > + > > > +out: > > > + if (ret) > > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > > + cru->scratch_phys); > > > + > > > + return ret; > > > +} > > > + > > > +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) > > > +{ > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > + > > > + rzg2l_cru_stop_streaming(cru); > > > + > > > + /* Free scratch buffer */ > > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > > + cru->scratch_phys); > > > + > > > + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); > > > +} > > > + > > > +static const struct vb2_ops rzg2l_cru_qops = { > > > + .queue_setup = rzg2l_cru_queue_setup, > > > + .buf_prepare = rzg2l_cru_buffer_prepare, > > > + .buf_queue = rzg2l_cru_buffer_queue, > > > + .start_streaming = rzg2l_cru_start_streaming_vq, > > > + .stop_streaming = rzg2l_cru_stop_streaming_vq, > > > + .wait_prepare = vb2_ops_wait_prepare, > > > + .wait_finish = vb2_ops_wait_finish, > > > +}; > > > + > > > +static irqreturn_t rzg2l_cru_irq(int irq, void *data) > > > +{ > > > + struct rzg2l_cru_dev *cru = data; > > > + unsigned int handled = 0; > > > + unsigned long flags; > > > + u32 irq_status; > > > + u32 amnmbs; > > > + int slot; > > > + > > > + spin_lock_irqsave(&cru->qlock, flags); > > > + > > > + irq_status = rzg2l_cru_read(cru, CRUnINTS); > > > + if (!irq_status) > > > + goto done; > > > + > > > + handled = 1; > > > + > > > + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); > > > + > > > + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ > > > + if (cru->state == RZG2L_CRU_DMA_STOPPED) { > > > + dev_dbg(cru->dev, "IRQ while state stopped\n"); > > > + goto done; > > > + } > > > + > > > + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ > > > + if (cru->state == RZG2L_CRU_DMA_STOPPING) { > > > + if (irq_status & CRUnINTS_SFS) > > > + dev_dbg(cru->dev, "IRQ while state stopping\n"); > > > + goto done; > > > + } > > > + > > > + /* Prepare for capture and update state */ > > > + amnmbs = rzg2l_cru_read(cru, AMnMBS); > > > + slot = amnmbs & AMnMBS_MBSTS; > > > + > > > + /* > > > + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). > > > + * Recalculate to get the current transfer complete MB. > > > + */ > > > + if (slot == 0) > > > + slot = cru->num_buf - 1; > > > + else > > > + slot--; > > > + > > > + /* > > > + * To hand buffers back in a known order to userspace start > > > + * to capture first from slot 0. > > > + */ > > > + if (cru->state == RZG2L_CRU_DMA_STARTING) { > > > + if (slot != 0) { > > > + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); > > > + goto done; > > > + } > > > + > > > + dev_dbg(cru->dev, "Capture start synced!\n"); > > > + cru->state = RZG2L_CRU_DMA_RUNNING; > > > + } > > > + > > > + /* Capture frame */ > > > + if (cru->queue_buf[slot]) { > > > + cru->queue_buf[slot]->field = cru->format.field; > > > + cru->queue_buf[slot]->sequence = cru->sequence; > > > + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); > > > + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, > > > + VB2_BUF_STATE_DONE); > > > + cru->queue_buf[slot] = NULL; > > > + } else { > > > + /* Scratch buffer was used, dropping frame. */ > > > + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); > > > + } > > > + > > > + cru->sequence++; > > > + > > > + /* Prepare for next frame */ > > > + rzg2l_cru_fill_hw_slot(cru, slot); > > > + > > > +done: > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > + > > > + return IRQ_RETVAL(handled); > > > +} > > > + > > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) > > > +{ > > > + mutex_destroy(&cru->lock); > > > + > > > + v4l2_device_unregister(&cru->v4l2_dev); > > > + reset_control_assert(cru->presetn); > > > +} > > > + > > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq) > > > +{ > > > + struct vb2_queue *q = &cru->queue; > > > + unsigned int i; > > > + int ret; > > > + > > > + ret = reset_control_deassert(cru->presetn); > > > + if (ret) { > > > + dev_err(cru->dev, "failed to deassert presetn\n"); > > > + return ret; > > > + } > > > > Shouldn't this be done when starting streaming instead ? > > Agreed, Ill move it there. > > > > + > > > + /* Initialize the top-level structure */ > > > + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); > > > + if (ret) > > > + return ret; > > > + > > > + mutex_init(&cru->lock); > > > + INIT_LIST_HEAD(&cru->buf_list); > > > + > > > + spin_lock_init(&cru->qlock); > > > + > > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > > + > > > + for (i = 0; i < HW_BUFFER_MAX; i++) > > > + cru->queue_buf[i] = NULL; > > > + > > > + /* buffer queue */ > > > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > > > + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; > > > > No VB2_READ please, that's very inefficient. > > OK, I'll drop it. > > > > + q->lock = &cru->lock; > > > + q->drv_priv = cru; > > > + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); > > > + q->ops = &rzg2l_cru_qops; > > > + q->mem_ops = &vb2_dma_contig_memops; > > > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > > > + q->min_buffers_needed = 4; > > > > Does the hardware really require 4 buffers to operate ? > > v4l2-compliance complains about sequnce mismatch when set to 3. Then maybe there's another problem somewhere ? What's the minimum number of buffers the hardware can operate with ? > > > + q->dev = cru->dev; > > > + > > > + ret = vb2_queue_init(q); > > > + if (ret < 0) { > > > + dev_err(cru->dev, "failed to initialize VB2 queue\n"); > > > + goto error; > > > + } > > > + > > > + /* IRQ */ > > > + ret = devm_request_irq(cru->dev, irq, rzg2l_cru_irq, IRQF_SHARED, > > > + KBUILD_MODNAME, cru); > > > + if (ret) { > > > + dev_err(cru->dev, "failed to request irq\n"); > > > + goto error; > > > + } > > > + > > > + return 0; > > > + > > > +error: > > > + rzg2l_cru_dma_unregister(cru); > > > + return ret; > > > +} > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > new file mode 100644 > > > index 000000000000..c565597f5769 > > > --- /dev/null > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > @@ -0,0 +1,368 @@ > > > +// SPDX-License-Identifier: GPL-2.0+ > > > +/* > > > + * Driver for Renesas RZ/G2L CRU > > > + * > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > + * > > > + * Based on Renesas R-Car VIN > > > + * Copyright (C) 2016 Renesas Electronics Corp. > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > + * Copyright (C) 2008 Magnus Damm > > > + */ > > > + > > > +#include <linux/clk.h> > > > + > > > +#include <media/v4l2-event.h> > > > +#include <media/v4l2-ioctl.h> > > > +#include <media/v4l2-mc.h> > > > +#include <media/v4l2-rect.h> > > > + > > > +#include "rzg2l-cru.h" > > > + > > > +#define RZG2L_CRU_DEFAULT_FORMAT V4L2_PIX_FMT_UYVY > > > +#define RZG2L_CRU_DEFAULT_WIDTH 800 > > > +#define RZG2L_CRU_DEFAULT_HEIGHT 600 > > > +#define RZG2L_CRU_DEFAULT_FIELD V4L2_FIELD_NONE > > > +#define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB > > > + > > > +/* ----------------------------------------------------------------------------- > > > + * Format Conversions > > > + */ > > > + > > > +static const struct v4l2_format_info rzg2l_cru_formats[] = { > > > + { > > > + .format = V4L2_PIX_FMT_UYVY, > > > + .bpp[0] = 2, > > > + }, > > > +}; > > > + > > > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format) > > > +{ > > > + unsigned int i; > > > + > > > + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_formats); i++) > > > + if (rzg2l_cru_formats[i].format == format) > > > + return rzg2l_cru_formats + i; > > > + > > > + return NULL; > > > +} > > > + > > > +static u32 rzg2l_cru_format_bytesperline(struct v4l2_pix_format *pix) > > > +{ > > > + const struct v4l2_format_info *fmt; > > > + > > > + fmt = rzg2l_cru_format_from_pixel(pix->pixelformat); > > > + > > > + if (WARN_ON(!fmt)) > > > + return -EINVAL; > > > + > > > + return pix->width * fmt->bpp[0]; > > > +} > > > + > > > +static u32 rzg2l_cru_format_sizeimage(struct v4l2_pix_format *pix) > > > +{ > > > + return pix->bytesperline * pix->height; > > > +} > > > + > > > +static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, > > > + struct v4l2_pix_format *pix) > > > +{ > > > + if (!rzg2l_cru_format_from_pixel(pix->pixelformat)) > > > + pix->pixelformat = RZG2L_CRU_DEFAULT_FORMAT; > > > + > > > + switch (pix->field) { > > > + case V4L2_FIELD_TOP: > > > + case V4L2_FIELD_BOTTOM: > > > + case V4L2_FIELD_NONE: > > > + case V4L2_FIELD_INTERLACED_TB: > > > + case V4L2_FIELD_INTERLACED_BT: > > > + case V4L2_FIELD_INTERLACED: > > > + break; > > > + default: > > > + pix->field = RZG2L_CRU_DEFAULT_FIELD; > > > + break; > > > + } > > > + > > > + /* Limit to CRU capabilities */ > > > + v4l_bound_align_image(&pix->width, 320, CRU_MAX_INPUT_WIDTH, 1, > > > + &pix->height, 240, CRU_MAX_INPUT_HEIGHT, 2, 0); > > > + > > > + pix->bytesperline = rzg2l_cru_format_bytesperline(pix); > > > + pix->sizeimage = rzg2l_cru_format_sizeimage(pix); > > > + > > > + dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", > > > + pix->width, pix->height, pix->bytesperline, pix->sizeimage); > > > +} > > > + > > > +static void rzg2l_cru_try_format(struct rzg2l_cru_dev *cru, > > > + struct v4l2_pix_format *pix) > > > +{ > > > + /* > > > + * The V4L2 specification clearly documents the colorspace fields > > > + * as being set by drivers for capture devices. Using the values > > > + * supplied by userspace thus wouldn't comply with the API. Until > > > + * the API is updated force fixed values. > > > + */ > > > + pix->colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; > > > + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); > > > + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); > > > + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, > > > + pix->ycbcr_enc); > > > + > > > + rzg2l_cru_format_align(cru, pix); > > > +} > > > + > > > +static int rzg2l_cru_querycap(struct file *file, void *priv, > > > + struct v4l2_capability *cap) > > > +{ > > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > > + > > > + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); > > > + strscpy(cap->card, "RZG2L_CRU", sizeof(cap->card)); > > > + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", > > > + dev_name(cru->dev)); > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_try_fmt_vid_cap(struct file *file, void *priv, > > > + struct v4l2_format *f) > > > +{ > > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > > + > > > + rzg2l_cru_try_format(cru, &f->fmt.pix); > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_s_fmt_vid_cap(struct file *file, void *priv, > > > + struct v4l2_format *f) > > > +{ > > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > > + > > > + if (vb2_is_busy(&cru->queue)) > > > + return -EBUSY; > > > + > > > + rzg2l_cru_try_format(cru, &f->fmt.pix); > > > + > > > + cru->format = f->fmt.pix; > > > + > > > + cru->compose.top = 0; > > > + cru->compose.left = 0; > > > + cru->compose.width = cru->format.width; > > > + cru->compose.height = cru->format.height; > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_g_fmt_vid_cap(struct file *file, void *priv, > > > + struct v4l2_format *f) > > > +{ > > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > > + > > > + f->fmt.pix = cru->format; > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_enum_fmt_vid_cap(struct file *file, void *priv, > > > + struct v4l2_fmtdesc *f) > > > +{ > > > + if (f->index >= ARRAY_SIZE(rzg2l_cru_formats)) > > > + return -EINVAL; > > > + > > > + f->pixelformat = rzg2l_cru_formats[f->index].format; > > > + > > > + return 0; > > > +} > > > + > > > +static int rzg2l_cru_subscribe_event(struct v4l2_fh *fh, > > > + const struct v4l2_event_subscription *sub) > > > +{ > > > + switch (sub->type) { > > > + case V4L2_EVENT_SOURCE_CHANGE: > > > + return v4l2_event_subscribe(fh, sub, 4, NULL); > > > + } > > > + return v4l2_ctrl_subscribe_event(fh, sub); > > > +} > > > + > > > +static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { > > > + .vidioc_querycap = rzg2l_cru_querycap, > > > + .vidioc_try_fmt_vid_cap = rzg2l_cru_try_fmt_vid_cap, > > > + .vidioc_g_fmt_vid_cap = rzg2l_cru_g_fmt_vid_cap, > > > + .vidioc_s_fmt_vid_cap = rzg2l_cru_s_fmt_vid_cap, > > > + .vidioc_enum_fmt_vid_cap = rzg2l_cru_enum_fmt_vid_cap, > > > + > > > + .vidioc_reqbufs = vb2_ioctl_reqbufs, > > > + .vidioc_create_bufs = vb2_ioctl_create_bufs, > > > + .vidioc_querybuf = vb2_ioctl_querybuf, > > > + .vidioc_qbuf = vb2_ioctl_qbuf, > > > + .vidioc_dqbuf = vb2_ioctl_dqbuf, > > > + .vidioc_expbuf = vb2_ioctl_expbuf, > > > + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, > > > + .vidioc_streamon = vb2_ioctl_streamon, > > > + .vidioc_streamoff = vb2_ioctl_streamoff, > > > + > > > + .vidioc_log_status = v4l2_ctrl_log_status, > > > + .vidioc_subscribe_event = rzg2l_cru_subscribe_event, > > > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, > > > +}; > > > + > > > +/* ----------------------------------------------------------------------------- > > > + * Media controller file operations > > > + */ > > > + > > > +static int rzg2l_cru_open(struct file *file) > > > +{ > > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > > + int ret; > > > + > > > + ret = clk_prepare_enable(cru->pclk); > > > + if (ret) > > > + return ret; > > > + > > > + ret = clk_prepare_enable(cru->vclk); > > > + if (ret) > > > + goto disable_pclk; > > > + > > > + ret = clk_prepare_enable(cru->aclk); > > > + if (ret) > > > + goto disable_vclk; > > > + > > > + ret = mutex_lock_interruptible(&cru->lock); > > > + if (ret) > > > + goto disable_aclk; > > > + > > > + file->private_data = cru; > > > + ret = v4l2_fh_open(file); > > > + if (ret) > > > + goto err_unlock; > > > + > > > + ret = v4l2_pipeline_pm_get(&cru->vdev.entity); > > > + if (ret < 0) > > > + goto err_open; > > > + > > > + mutex_unlock(&cru->lock); > > > + > > > + return 0; > > > +err_open: > > > + v4l2_fh_release(file); > > > +err_unlock: > > > + mutex_unlock(&cru->lock); > > > +disable_aclk: > > > + clk_disable_unprepare(cru->aclk); > > > +disable_vclk: > > > + clk_disable_unprepare(cru->vclk); > > > +disable_pclk: > > > + clk_disable_unprepare(cru->pclk); > > > + > > > + return ret; > > > +} > > > + > > > +static int rzg2l_cru_release(struct file *file) > > > +{ > > > + struct rzg2l_cru_dev *cru = video_drvdata(file); > > > + int ret; > > > + > > > + mutex_lock(&cru->lock); > > > + > > > + /* the release helper will cleanup any on-going streaming. */ > > > + ret = _vb2_fop_release(file, NULL); > > > + > > > + v4l2_pipeline_pm_put(&cru->vdev.entity); > > > + clk_disable_unprepare(cru->aclk); > > > + clk_disable_unprepare(cru->vclk); > > > + clk_disable_unprepare(cru->pclk); > > > + > > > + mutex_unlock(&cru->lock); > > > + > > > + return ret; > > > +} > > > + > > > +static const struct v4l2_file_operations rzg2l_cru_fops = { > > > + .owner = THIS_MODULE, > > > + .unlocked_ioctl = video_ioctl2, > > > + .open = rzg2l_cru_open, > > > + .release = rzg2l_cru_release, > > > + .poll = vb2_fop_poll, > > > + .mmap = vb2_fop_mmap, > > > + .read = vb2_fop_read, > > > +}; > > > + > > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru) > > > +{ > > > + if (!video_is_registered(&cru->vdev)) > > > + return; > > > + > > > + v4l2_info(&cru->v4l2_dev, "Removed %s\n", > > > + video_device_node_name(&cru->vdev)); > > > > You can use dev_info(), but I'd actually use dev_dbg(). Same below. > > OK, I will switch to dev_dbg(). > > > > + > > > + /* Checks internally if vdev have been init or not */ > > > + video_unregister_device(&cru->vdev); > > > +} > > > + > > > +static void rzg2l_cru_notify(struct v4l2_subdev *sd, > > > + unsigned int notification, void *arg) > > > +{ > > > + struct rzg2l_cru_dev *cru = > > > + container_of(sd->v4l2_dev, struct rzg2l_cru_dev, v4l2_dev); > > > + struct v4l2_subdev *remote; > > > + struct media_pad *pad; > > > + > > > + pad = media_pad_remote_pad_first(&cru->pad); > > > + if (!pad) > > > + return; > > > + > > > + remote = media_entity_to_v4l2_subdev(pad->entity); > > > + if (remote != sd) > > > + return; > > > + > > > + switch (notification) { > > > + case V4L2_DEVICE_NOTIFY_EVENT: > > > + v4l2_event_queue(&cru->vdev, arg); > > > + break; > > > + } > > > +} > > > > Drop this, userspace should listen for events on the subdevices that > > generate them. > > OK so in that case I can completely get rid of rzg2l_cru_notify(). > > > > + > > > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru) > > > +{ > > > + struct video_device *vdev = &cru->vdev; > > > + int ret; > > > + > > > + cru->v4l2_dev.notify = rzg2l_cru_notify; > > > + > > > + /* video node */ > > > + vdev->v4l2_dev = &cru->v4l2_dev; > > > + vdev->queue = &cru->queue; > > > + snprintf(vdev->name, sizeof(vdev->name), "CRU output"); > > > + vdev->release = video_device_release_empty; > > > + vdev->lock = &cru->lock; > > > + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | > > > + V4L2_CAP_READWRITE; > > > > No read/write please. > > OK, I will drop it. > > > > + > > > + /* Set a default format */ > > > + cru->format.pixelformat = RZG2L_CRU_DEFAULT_FORMAT; > > > + cru->format.width = RZG2L_CRU_DEFAULT_WIDTH; > > > + cru->format.height = RZG2L_CRU_DEFAULT_HEIGHT; > > > + cru->format.field = RZG2L_CRU_DEFAULT_FIELD; > > > + cru->format.colorspace = RZG2L_CRU_DEFAULT_COLORSPACE; > > > + > > > + vdev->device_caps |= V4L2_CAP_IO_MC; > > > + vdev->fops = &rzg2l_cru_fops; > > > + vdev->ioctl_ops = &rzg2l_cru_ioctl_ops; > > > + > > > + rzg2l_cru_format_align(cru, &cru->format); > > > > I'd perform all this initialization in an init function called at probe > > time, with only the actual registration left here, to be done at bound > > time. > > Agrreed, Ill create a rzg2l_cru_v4l2_init() for it.
Hi Laurent, On Mon, Sep 26, 2022 at 9:59 AM Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > Hi Prabhakar, > > On Fri, Sep 23, 2022 at 08:02:12PM +0100, Lad, Prabhakar wrote: > > On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart wrote: > > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > > > > > Based on a patch in the BSP by Hien Huynh > > > > <hien.huynh.px@renesas.com> > > > > > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > > --- > > > > v1 -> v2 > > > > * No change > > > > > > > > RFC v2 -> v1 > > > > * Moved the driver to renesas folder > > > > * Fixed review comments pointed by Jacopo > > > > > > > > RFC v1 -> RFC v2 > > > > * Dropped group > > > > * Dropped CSI subdev and implemented as new driver > > > > * Dropped "mc_" from function names > > > > * Moved the driver to renesas folder > > > > --- > > > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > > > > > I'd merge those two files together, they both handle the video node. > > > There's a comment below that recommends adding a subdev, that should > > > then go to a separate file. > > > > OK, I'll merge these files into rzg2l-video.c. > > > > > > 6 files changed, 1669 insertions(+) > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > index 57c40bb499df..08ff0e96b3f5 100644 > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > > > > > To compile this driver as a module, choose M here: the > > > > module will be called rzg2l-csi2. > > > > + > > > > +config VIDEO_RZG2L_CRU > > > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > > + depends on V4L_PLATFORM_DRIVERS > > > > + depends on VIDEO_DEV && OF > > > > + select MEDIA_CONTROLLER > > > > + select V4L2_FWNODE > > > > + select VIDEOBUF2_DMA_CONTIG > > > > + select VIDEO_RZG2L_CSI2 > > > > > > Is this required, can't the CRU be used with a parallel sensor without > > > the CSI-2 receiver ? > > > > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > > > > > + select VIDEO_V4L2_SUBDEV_API > > > > + help > > > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > > > + Unit (CRU) driver. > > > > + > > > > + To compile this driver as a module, choose M here: the > > > > + module will be called rzg2l-cru. > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > index 91ea97a944e6..7628809e953f 100644 > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > @@ -1,3 +1,6 @@ > > > > # SPDX-License-Identifier: GPL-2.0 > > > > > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > > > + > > > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > new file mode 100644 > > > > index 000000000000..b5d4110b1913 > > > > --- /dev/null > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > @@ -0,0 +1,395 @@ > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > +/* > > > > + * Driver for Renesas RZ/G2L CRU > > > > + * > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > + * > > > > + * Based on Renesas R-Car VIN > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > + * Copyright (C) 2008 Magnus Damm > > > > + */ > > > > + > > > > +#include <linux/clk.h> > > > > +#include <linux/module.h> > > > > +#include <linux/mod_devicetable.h> > > > > +#include <linux/of.h> > > > > +#include <linux/of_device.h> > > > > +#include <linux/of_graph.h> > > > > +#include <linux/platform_device.h> > > > > +#include <linux/pm_runtime.h> > > > > + > > > > +#include <media/v4l2-fwnode.h> > > > > +#include <media/v4l2-mc.h> > > > > + > > > > +#include "rzg2l-cru.h" > > > > + > > > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > > > > > As this macro is only used to get the rzg2l_cru_dev pointer from the > > > v4l2_async_notifier pointer, you can replace it with > > > > > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > > > > > I would also turn it into a static inline function for additional > > > compile-time type safety. > > > > OK, I will do it as mentioned above. > > > > > > + > > > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > > > + unsigned int notification) > > > > +{ > > > > + struct media_entity *entity; > > > > + struct rzg2l_cru_dev *cru; > > > > + struct media_pad *csi_pad; > > > > + struct v4l2_subdev *sd; > > > > + int ret; > > > > + > > > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + /* Only care about link enablement for CRU nodes. */ > > > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > > > + return 0; > > > > + > > > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > > > + /* > > > > + * Don't allow link changes if any entity in the graph is > > > > + * streaming, modifying the CHSEL register fields can disrupt > > > > + * running streams. > > > > + */ > > > > + media_device_for_each_entity(entity, &cru->mdev) > > > > + if (media_entity_is_streaming(entity)) > > > > + return -EBUSY; > > > > + > > > > + mutex_lock(&cru->mdev_lock); > > > > + > > > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > > > + if (csi_pad) { > > > > + ret = -EMLINK; > > > > + goto out; > > > > + } > > > > + > > > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > > > + if (cru->csi.subdev == sd) { > > > > + cru->csi.channel = link->source->index - 1; > > > > + cru->is_csi = true; > > > > + } else { > > > > + ret = -ENODEV; > > > > + } > > > > + > > > > +out: > > > > + mutex_unlock(&cru->mdev_lock); > > > > + > > > > + return ret; > > > > +} > > > > + > > > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > > > + .link_notify = rzg2l_cru_csi2_link_notify, > > > > +}; > > > > + > > > > +/* ----------------------------------------------------------------------------- > > > > + * Group async notifier > > > > + */ > > > > + > > > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > + unsigned int i; > > > > + int ret; > > > > + > > > > + ret = media_device_register(&cru->mdev); > > > > + if (ret) > > > > + return ret; > > > > > > I'd move the v4l2_device_register() call here, as it's the V4L2 > > > counterpart of the media device, and handling them together would be > > > best. > > > > OK. > > > > > > + > > > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > > > + if (ret) { > > > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > > > + return ret; > > > > + } > > > > + > > > > + if (!video_is_registered(&cru->vdev)) { > > > > > > Can this happen ? > > > > No, I'll drop this check. > > > > > > + ret = rzg2l_cru_v4l2_register(cru); > > > > + if (ret) > > > > + return ret; > > > > + } > > > > + > > > > + /* Create all media device links between CRU and CSI-2's. */ > > > > + /* > > > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > > > + * should be implemented by streams API which is under development > > > > + * so for now just link it to VC0 > > > > + */ > > > > > > The streams API won't require more links, so I'd drop the comment and > > > the loop and create a single link. > > > > OK. > > > > > > + for (i = 1; i <= 1; i++) { > > > > + struct media_entity *source, *sink; > > > > + > > > > + source = &cru->csi.subdev->entity; > > > > + sink = &cru->vdev.entity; > > > > > > Hmmm... I'd recommend adding a subdev to model the image processing > > > pipeline of the CRU, between the CSI-2 receiver and the video node. That > > > will help when you'll add support for parallel sensors, and it will also > > > be needed by the streams API to select which virtual channel to capture. > >/ > > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? > > I think MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER would be more > appropriate. > OK I will use MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER. As this will be just like a switch should I be implementing the get_fmt/set_fmt callbacks? > > > > + > > > > + ret = media_create_pad_link(source, i, sink, 0, 0); > > > > + if (ret) { > > > > + dev_err(cru->dev, "Error adding link from %s to %s\n", > > > > + source->name, sink->name); > > > > + break; > > > > + } > > > > + } > > > > + > > > > + return ret; > > > > +} > > > > + > > > > +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, > > > > + struct v4l2_subdev *subdev, > > > > + struct v4l2_async_subdev *asd) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > + > > > > + rzg2l_cru_v4l2_unregister(cru); > > > > + > > > > + mutex_lock(&cru->mdev_lock); > > > > + > > > > + if (cru->csi.asd == asd) { > > > > + cru->csi.subdev = NULL; > > > > + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); > > > > + } > > > > + > > > > + mutex_unlock(&cru->mdev_lock); > > > > + > > > > + media_device_unregister(&cru->mdev); > > > > +} > > > > + > > > > +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, > > > > + struct v4l2_subdev *subdev, > > > > + struct v4l2_async_subdev *asd) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > + > > > > + mutex_lock(&cru->mdev_lock); > > > > + > > > > + if (cru->csi.asd == asd) { > > > > + cru->csi.subdev = subdev; > > > > + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); > > > > + } > > > > + > > > > + mutex_unlock(&cru->mdev_lock); > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { > > > > + .bound = rzg2l_cru_group_notify_bound, > > > > + .unbind = rzg2l_cru_group_notify_unbind, > > > > + .complete = rzg2l_cru_group_notify_complete, > > > > +}; > > > > + > > > > +static int rvin_mc_parse_of(struct rzg2l_cru_dev *cru, unsigned int id) > > > > > > The id parameter is always 0, I'd drop it. > > > > Agreed, I will drop it. > > > > > > +{ > > > > + struct v4l2_fwnode_endpoint vep = { > > > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > > > + }; > > > > + struct fwnode_handle *ep, *fwnode; > > > > + struct v4l2_async_subdev *asd; > > > > + int ret; > > > > + > > > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, id, 0); > > > > + if (!ep) > > > > + return 0; > > > > + > > > > + fwnode = fwnode_graph_get_remote_endpoint(ep); > > > > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > > > > + fwnode_handle_put(ep); > > > > + if (ret) { > > > > + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); > > > > + ret = -EINVAL; > > > > + goto out; > > > > + } > > > > + > > > > + if (!of_device_is_available(to_of_node(fwnode))) { > > > > + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", > > > > + to_of_node(fwnode)); > > > > + ret = -ENOTCONN; > > > > + goto out; > > > > + } > > > > + > > > > + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, > > > > + struct v4l2_async_subdev); > > > > + if (IS_ERR(asd)) { > > > > + ret = PTR_ERR(asd); > > > > + goto out; > > > > + } > > > > + > > > > + cru->csi.asd = asd; > > > > + > > > > + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", > > > > + to_of_node(fwnode), vep.base.id); > > > > +out: > > > > + fwnode_handle_put(fwnode); > > > > + > > > > + return ret; > > > > +} > > > > + > > > > +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) > > > > +{ > > > > + int ret; > > > > + > > > > + v4l2_async_nf_init(&cru->notifier); > > > > + > > > > + ret = rvin_mc_parse_of(cru, 0); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + cru->notifier.ops = &rzg2l_cru_async_ops; > > > > + > > > > + if (list_empty(&cru->notifier.asd_list)) > > > > + return 0; > > > > + > > > > + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); > > > > + if (ret < 0) { > > > > + dev_err(cru->dev, "Notifier registration failed\n"); > > > > + v4l2_async_nf_cleanup(&cru->notifier); > > > > + return ret; > > > > + } > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static int rzg2l_cru_csi2_init(struct rzg2l_cru_dev *cru) > > > > > > The naming is a bit weird, as this isn't related to CSI-2. I would name > > > the function rzg2l_cru_media_init(). > > > > Agreed, I will rename it. > > > > > > +{ > > > > + struct media_device *mdev = NULL; > > > > + const struct of_device_id *match; > > > > + int ret; > > > > + > > > > + cru->pad.flags = MEDIA_PAD_FL_SINK; > > > > + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + mutex_init(&cru->mdev_lock); > > > > + mdev = &cru->mdev; > > > > + mdev->dev = cru->dev; > > > > + mdev->ops = &rzg2l_cru_media_ops; > > > > + > > > > + match = of_match_node(cru->dev->driver->of_match_table, > > > > + cru->dev->of_node); > > > > + > > > > + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); > > > > + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); > > > > + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", > > > > + dev_name(mdev->dev)); > > > > + > > > > + cru->v4l2_dev.mdev = &cru->mdev; > > > > + > > > > + media_device_init(mdev); > > > > + > > > > + ret = rzg2l_cru_mc_parse_of_graph(cru); > > > > + if (ret) { > > > > + mutex_lock(&cru->mdev_lock); > > > > + cru->v4l2_dev.mdev = NULL; > > > > + mutex_unlock(&cru->mdev_lock); > > > > + } > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static int rzg2l_cru_probe(struct platform_device *pdev) > > > > +{ > > > > + struct rzg2l_cru_dev *cru; > > > > + int irq, ret; > > > > + > > > > + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); > > > > + if (!cru) > > > > + return -ENOMEM; > > > > + > > > > + cru->base = devm_platform_ioremap_resource(pdev, 0); > > > > + if (IS_ERR(cru->base)) > > > > + return PTR_ERR(cru->base); > > > > + > > > > + cru->presetn = devm_reset_control_get(&pdev->dev, "presetn"); > > > > + if (IS_ERR(cru->presetn)) > > > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), > > > > + "failed to get cpg presetn\n"); > > > > + > > > > + cru->aresetn = devm_reset_control_get(&pdev->dev, "aresetn"); > > > > + if (IS_ERR(cru->aresetn)) > > > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), > > > > + "failed to get cpg aresetn\n"); > > > > + > > > > + cru->vclk = devm_clk_get(&pdev->dev, "vclk"); > > > > + if (IS_ERR(cru->vclk)) { > > > > + dev_err(&pdev->dev, "Failed to get vclk"); > > > > + return PTR_ERR(cru->vclk); > > > > > > You could use dev_err_probe() here too (as well as below). > > > > OK. > > > > > > + } > > > > + > > > > + cru->pclk = devm_clk_get(&pdev->dev, "pclk"); > > > > + if (IS_ERR(cru->pclk)) { > > > > + dev_err(&pdev->dev, "Failed to get pclk"); > > > > + return PTR_ERR(cru->pclk); > > > > + } > > > > + > > > > + cru->aclk = devm_clk_get(&pdev->dev, "aclk"); > > > > + if (IS_ERR(cru->aclk)) { > > > > + dev_err(&pdev->dev, "Failed to get aclk"); > > > > + return PTR_ERR(cru->aclk); > > > > + } > > > > + > > > > + cru->dev = &pdev->dev; > > > > + cru->info = of_device_get_match_data(&pdev->dev); > > > > + > > > > + irq = platform_get_irq(pdev, 0); > > > > + if (irq < 0) > > > > + return irq; > > > > + > > > > + ret = rzg2l_cru_dma_register(cru, irq); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + platform_set_drvdata(pdev, cru); > > > > + > > > > + ret = rzg2l_cru_csi2_init(cru); > > > > + if (ret) > > > > + goto error_dma_unregister; > > > > + > > > > + cru->num_buf = HW_BUFFER_DEFAULT; > > > > + pm_suspend_ignore_children(&pdev->dev, true); > > > > + pm_runtime_enable(&pdev->dev); > > > > + > > > > + return 0; > > > > + > > > > +error_dma_unregister: > > > > + rzg2l_cru_dma_unregister(cru); > > > > + > > > > + return ret; > > > > +} > > > > + > > > > +static const struct of_device_id rzg2l_cru_of_id_table[] = { > > > > + { > > > > + .compatible = "renesas,rzg2l-cru", > > > > + }, > > > > + { /* sentinel */ } > > > > +}; > > > > +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); > > > > + > > > > +static int rzg2l_cru_remove(struct platform_device *pdev) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); > > > > + > > > > + pm_runtime_disable(&pdev->dev); > > > > + > > > > + rzg2l_cru_v4l2_unregister(cru); > > > > + > > > > + v4l2_async_nf_unregister(&cru->notifier); > > > > + v4l2_async_nf_cleanup(&cru->notifier); > > > > + > > > > + media_device_cleanup(&cru->mdev); > > > > + mutex_destroy(&cru->mdev_lock); > > > > + cru->v4l2_dev.mdev = NULL; > > > > > > Is this needed ? > > > > Not required. > > > > > > + > > > > + rzg2l_cru_dma_unregister(cru); > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static struct platform_driver rzg2l_cru_driver = { > > > > + .driver = { > > > > + .name = "rzg2l-cru", > > > > + .of_match_table = rzg2l_cru_of_id_table, > > > > + }, > > > > + .probe = rzg2l_cru_probe, > > > > + .remove = rzg2l_cru_remove, > > > > > > No PM ? > > > > I plan to gradually add at a later point. > > > > > > +}; > > > > + > > > > +module_platform_driver(rzg2l_cru_driver); > > > > + > > > > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > > > > +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); > > > > +MODULE_LICENSE("GPL"); > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > new file mode 100644 > > > > index 000000000000..a834680a3200 > > > > --- /dev/null > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > @@ -0,0 +1,152 @@ > > > > +/* SPDX-License-Identifier: GPL-2.0+ */ > > > > +/* > > > > + * Driver for Renesas RZ/G2L CRU > > > > + * > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > + * > > > > > > Extra blank line. > > > > Oops, I will drop it. > > > > > > + */ > > > > + > > > > +#ifndef __RZG2L_CRU__ > > > > +#define __RZG2L_CRU__ > > > > + > > > > +#include <linux/reset.h> > > > > + > > > > +#include <media/v4l2-async.h> > > > > +#include <media/v4l2-ctrls.h> > > > > +#include <media/v4l2-dev.h> > > > > +#include <media/v4l2-device.h> > > > > +#include <media/videobuf2-v4l2.h> > > > > + > > > > +/* Number of HW buffers */ > > > > +#define HW_BUFFER_MAX 8 > > > > +#define HW_BUFFER_DEFAULT 3 > > > > > > Could you prefix macro names with CRU_ (or RZG2L_CRU_, up to you) ? > > > These names are a bit generic and could lead to clashes. > > > > Agreed, I will rename it. > > > > > > + > > > > +/* Address alignment mask for HW buffers */ > > > > +#define HW_BUFFER_MASK 0x1ff > > > > + > > > > +/* Maximum number of CSI2 virtual channels */ > > > > +#define CSI2_VCHANNEL 4 > > > > + > > > > +#define CRU_MAX_INPUT_WIDTH 2800 > > > > +#define CRU_MAX_INPUT_HEIGHT 4095 > > > > + > > > > +/** > > > > + * enum rzg2l_cru_dma_state - DMA states > > > > + * @RZG2L_CRU_DMA_STOPPED: No operation in progress > > > > + * @RZG2L_CRU_DMA_STARTING: Capture starting up > > > > + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers > > > > + * @RZG2L_CRU_DMA_STOPPING: Stopping operation > > > > + */ > > > > +enum rzg2l_cru_dma_state { > > > > + RZG2L_CRU_DMA_STOPPED = 0, > > > > + RZG2L_CRU_DMA_STARTING, > > > > + RZG2L_CRU_DMA_RUNNING, > > > > + RZG2L_CRU_DMA_STOPPING, > > > > +}; > > > > + > > > > +struct rzg2l_cru_csi { > > > > + struct v4l2_async_subdev *asd; > > > > + struct v4l2_subdev *subdev; > > > > + u32 channel; > > > > +}; > > > > + > > > > +/** > > > > + * struct rzg2l_cru_dev - Renesas CRU device structure > > > > + * @dev: (OF) device > > > > + * @base: device I/O register space remapped to virtual memory > > > > + * @info: info about CRU instance > > > > + * > > > > + * @presetn: CRU_PRESETN reset line > > > > + * @aresetn: CRU_ARESETN reset line > > > > + * > > > > + * @vclk: CRU Main clock > > > > + * @pclk: CPU Register access clock > > > > + * @aclk: CRU image transfer clock > > > > + * > > > > + * @vdev: V4L2 video device associated with CRU > > > > + * @v4l2_dev: V4L2 device > > > > + * @ctrl_handler: V4L2 control handler > > > > + * @num_buf: Holds the current number of buffers enabled > > > > + * @notifier: V4L2 asynchronous subdevs notifier > > > > + * > > > > + * @csi: CSI info > > > > + * @mdev: media device > > > > + * @mdev_lock: protects the count, notifier and csi members > > > > + * @pad: media pad for the video device entity > > > > + * > > > > + * @lock: protects @queue > > > > + * @queue: vb2 buffers queue > > > > + * @scratch: cpu address for scratch buffer > > > > + * @scratch_phys: physical address of the scratch buffer > > > > + * > > > > + * @qlock: protects @queue_buf, @buf_list, @sequence > > > > + * @state > > > > + * @queue_buf: Keeps track of buffers given to HW slot > > > > + * @buf_list: list of queued buffers > > > > + * @sequence: V4L2 buffers sequence number > > > > + * @state: keeps track of operation state > > > > + * > > > > + * @is_csi: flag to mark the CRU as using a CSI-2 subdevice > > > > + * > > > > + * @input_is_yuv: flag to mark the input format of CRU > > > > + * @output_is_yuv: flag to mark the output format of CRU > > > > + * > > > > + * @mbus_code: media bus format code > > > > + * @format: active V4L2 pixel format > > > > + * > > > > + * @compose: active composing > > > > + */ > > > > +struct rzg2l_cru_dev { > > > > + struct device *dev; > > > > + void __iomem *base; > > > > + const struct rzg2l_cru_info *info; > > > > + > > > > + struct reset_control *presetn; > > > > + struct reset_control *aresetn; > > > > + > > > > + struct clk *vclk; > > > > + struct clk *pclk; > > > > + struct clk *aclk; > > > > + > > > > + struct video_device vdev; > > > > + struct v4l2_device v4l2_dev; > > > > + u8 num_buf; > > > > + > > > > + struct v4l2_async_notifier notifier; > > > > + > > > > + struct rzg2l_cru_csi csi; > > > > + struct media_device mdev; > > > > + struct mutex mdev_lock; > > > > + struct media_pad pad; > > > > + > > > > + struct mutex lock; > > > > + struct vb2_queue queue; > > > > + void *scratch; > > > > + dma_addr_t scratch_phys; > > > > + > > > > + spinlock_t qlock; > > > > + struct vb2_v4l2_buffer *queue_buf[HW_BUFFER_MAX]; > > > > + struct list_head buf_list; > > > > + unsigned int sequence; > > > > + enum rzg2l_cru_dma_state state; > > > > + > > > > + bool is_csi; > > > > + > > > > + bool input_is_yuv; > > > > + bool output_is_yuv; > > > > + > > > > + u32 mbus_code; > > > > + struct v4l2_pix_format format; > > > > + > > > > + struct v4l2_rect compose; > > > > +}; > > > > + > > > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq); > > > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); > > > > + > > > > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru); > > > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru); > > > > + > > > > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); > > > > + > > > > +#endif > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > new file mode 100644 > > > > index 000000000000..44efd071f562 > > > > --- /dev/null > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > @@ -0,0 +1,734 @@ > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > +/* > > > > + * Driver for Renesas RZ/G2L CRU > > > > + * > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > + * > > > > + * Based on Renesas R-Car VIN > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > + * Copyright (C) 2008 Magnus Damm > > > > + */ > > > > + > > > > +#include <linux/clk.h> > > > > +#include <linux/delay.h> > > > > +#include <linux/interrupt.h> > > > > + > > > > +#include <media/videobuf2-dma-contig.h> > > > > + > > > > +#include "rzg2l-cru.h" > > > > +#include "rzg2l-csi2.h" > > > > + > > > > +/* HW CRU Registers Definition */ > > > > +/* CRU Control Register */ > > > > +#define CRUnCTRL 0x0 > > > > +#define CRUnCTRL_VINSEL(x) ((x) << 0) > > > > + > > > > +/* CRU Interrupt Enable Register */ > > > > +#define CRUnIE 0x4 > > > > +#define CRUnIE_SFE BIT(16) > > > > +#define CRUnIE_EFE BIT(17) > > > > + > > > > +/* CRU Interrupt Status Register */ > > > > +#define CRUnINTS 0x8 > > > > +#define CRUnINTS_SFS BIT(16) > > > > + > > > > +/* CRU Reset Register */ > > > > +#define CRUnRST 0xc > > > > +#define CRUnRST_VRESETN BIT(0) > > > > + > > > > +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ > > > > +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) > > > > + > > > > +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ > > > > +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) > > > > + > > > > +/* Memory Bank Enable Register for CRU Image Data */ > > > > +#define AMnMBVALID 0x148 > > > > +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) > > > > + > > > > +/* Memory Bank Status Register for CRU Image Data */ > > > > +#define AMnMBS 0x14c > > > > +#define AMnMBS_MBSTS 0x7 > > > > + > > > > +/* AXI Master FIFO Pointer Register for CRU Image Data */ > > > > +#define AMnFIFOPNTR 0x168 > > > > +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) > > > > +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) > > > > + > > > > +/* AXI Master Transfer Stop Register for CRU Image Data */ > > > > +#define AMnAXISTP 0x174 > > > > +#define AMnAXISTP_AXI_STOP BIT(0) > > > > + > > > > +/* AXI Master Transfer Stop Status Register for CRU Image Data */ > > > > +#define AMnAXISTPACK 0x178 > > > > +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) > > > > + > > > > +/* CRU Image Processing Enable Register */ > > > > +#define ICnEN 0x200 > > > > +#define ICnEN_ICEN BIT(0) > > > > + > > > > +/* CRU Image Processing Main Control Register */ > > > > +#define ICnMC 0x208 > > > > +#define ICnMC_CSCTHR BIT(5) > > > > +#define ICnMC_INF_YUV8_422 (0x1e << 16) > > > > +#define ICnMC_INF_USER (0x30 << 16) > > > > +#define ICnMC_VCSEL(x) ((x) << 22) > > > > +#define ICnMC_INF_MASK GENMASK(21, 16) > > > > + > > > > +/* CRU Module Status Register */ > > > > +#define ICnMS 0x254 > > > > +#define ICnMS_IA BIT(2) > > > > + > > > > +/* CRU Data Output Mode Register */ > > > > +#define ICnDMR 0x26c > > > > +#define ICnDMR_YCMODE_UYVY (1 << 4) > > > > + > > > > +#define RZG2L_TIMEOUT_MS 100 > > > > +#define RZG2L_RETRIES 10 > > > > + > > > > +struct rzg2l_cru_buffer { > > > > + struct vb2_v4l2_buffer vb; > > > > + struct list_head list; > > > > +}; > > > > + > > > > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > > > > + struct rzg2l_cru_buffer, \ > > > > + vb)->list) > > > > + > > > > +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) > > > > +{ > > > > + iowrite32(value, cru->base + offset); > > > > +} > > > > + > > > > +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) > > > > +{ > > > > + return ioread32(cru->base + offset); > > > > +} > > > > + > > > > +/* Need to hold qlock before calling */ > > > > +static void return_unused_buffers(struct rzg2l_cru_dev *cru, > > > > + enum vb2_buffer_state state) > > > > +{ > > > > + struct rzg2l_cru_buffer *buf, *node; > > > > + unsigned long flags; > > > > + unsigned int i; > > > > + > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > + for (i = 0; i < cru->num_buf; i++) { > > > > + if (cru->queue_buf[i]) { > > > > + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, > > > > + state); > > > > + cru->queue_buf[i] = NULL; > > > > + } > > > > + } > > > > + > > > > + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { > > > > + vb2_buffer_done(&buf->vb.vb2_buf, state); > > > > + list_del(&buf->list); > > > > + } > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > +} > > > > + > > > > +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, > > > > + unsigned int *nplanes, unsigned int sizes[], > > > > + struct device *alloc_devs[]) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > > + > > > > + /* Make sure the image size is large enough. */ > > > > + if (*nplanes) > > > > + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; > > > > + > > > > + *nplanes = 1; > > > > + sizes[0] = cru->format.sizeimage; > > > > + > > > > + return 0; > > > > +}; > > > > + > > > > +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > > > + unsigned long size = cru->format.sizeimage; > > > > + > > > > + if (vb2_plane_size(vb, 0) < size) { > > > > + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", > > > > + vb2_plane_size(vb, 0), size); > > > > + return -EINVAL; > > > > + } > > > > + > > > > + vb2_set_plane_payload(vb, 0, size); > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) > > > > +{ > > > > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > > > + unsigned long flags; > > > > + > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > + > > > > + list_add_tail(to_buf_list(vbuf), &cru->buf_list); > > > > + > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > +} > > > > + > > > > +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, > > > > + struct v4l2_subdev *sd, > > > > + struct media_pad *pad) > > > > +{ > > > > + struct v4l2_subdev_format fmt = { > > > > + .which = V4L2_SUBDEV_FORMAT_ACTIVE, > > > > + }; > > > > + > > > > + fmt.pad = pad->index; > > > > + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) > > > > + return -EPIPE; > > > > + > > > > + if (cru->is_csi) { > > > > + switch (fmt.format.code) { > > > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > > > + break; > > > > + default: > > > > + return -EPIPE; > > > > + } > > > > + } > > > > + cru->mbus_code = fmt.format.code; > > > > + > > > > + switch (fmt.format.field) { > > > > + case V4L2_FIELD_TOP: > > > > + case V4L2_FIELD_BOTTOM: > > > > + case V4L2_FIELD_NONE: > > > > + case V4L2_FIELD_INTERLACED_TB: > > > > + case V4L2_FIELD_INTERLACED_BT: > > > > + case V4L2_FIELD_INTERLACED: > > > > + case V4L2_FIELD_SEQ_TB: > > > > + case V4L2_FIELD_SEQ_BT: > > > > + break; > > > > + default: > > > > + return -EPIPE; > > > > + } > > > > + > > > > + if (fmt.format.width != cru->format.width || > > > > + fmt.format.height != cru->format.height || > > > > + fmt.format.code != cru->mbus_code) > > > > + return -EPIPE; > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, > > > > + int slot, dma_addr_t addr) > > > > +{ > > > > + const struct v4l2_format_info *fmt; > > > > + int offsetx, offsety; > > > > + dma_addr_t offset; > > > > + > > > > + fmt = rzg2l_cru_format_from_pixel(cru->format.pixelformat); > > > > + > > > > + /* > > > > + * There is no HW support for composition do the best we can > > > > + * by modifying the buffer offset > > > > + */ > > > > + offsetx = cru->compose.left * fmt->bpp[0]; > > > > + offsety = cru->compose.top * cru->format.bytesperline; > > > > + offset = addr + offsetx + offsety; > > > > + > > > > + /* > > > > + * The address needs to be 512 bytes aligned. Driver should never accept > > > > + * settings that do not satisfy this in the first place... > > > > + */ > > > > + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) > > > > + return; > > > > + > > > > + /* Currently, we just use the buffer in 32 bits address */ > > > > + rzg2l_cru_write(cru, AMnMBxADDRL(slot), offset); > > > > + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); > > > > +} > > > > + > > > > +/* > > > > + * Moves a buffer from the queue to the HW slot. If no buffer is > > > > + * available use the scratch buffer. The scratch buffer is never > > > > + * returned to userspace, its only function is to enable the capture > > > > + * loop to keep running. > > > > + */ > > > > +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) > > > > +{ > > > > + struct vb2_v4l2_buffer *vbuf; > > > > + struct rzg2l_cru_buffer *buf; > > > > + dma_addr_t phys_addr; > > > > + > > > > + /* A already populated slot shall never be overwritten. */ > > > > + if (WARN_ON(cru->queue_buf[slot])) > > > > + return; > > > > + > > > > + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); > > > > + > > > > + if (list_empty(&cru->buf_list)) { > > > > + cru->queue_buf[slot] = NULL; > > > > + phys_addr = cru->scratch_phys; > > > > + } else { > > > > + /* Keep track of buffer we give to HW */ > > > > + buf = list_entry(cru->buf_list.next, > > > > + struct rzg2l_cru_buffer, list); > > > > + vbuf = &buf->vb; > > > > + list_del_init(to_buf_list(vbuf)); > > > > + cru->queue_buf[slot] = vbuf; > > > > + > > > > + /* Setup DMA */ > > > > + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); > > > > + } > > > > + > > > > + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); > > > > +} > > > > + > > > > +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) > > > > +{ > > > > + unsigned int slot; > > > > + > > > > + /* > > > > + * Set image data memory banks. > > > > + * Currently, we will use maximum address. > > > > + */ > > > > + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); > > > > + > > > > + for (slot = 0; slot < cru->num_buf; slot++) > > > > + rzg2l_cru_fill_hw_slot(cru, slot); > > > > +} > > > > + > > > > +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru) > > > > +{ > > > > + u32 icnmc; > > > > + > > > > + switch (cru->mbus_code) { > > > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > > > + icnmc = ICnMC_INF_YUV8_422; > > > > + cru->input_is_yuv = true; > > > > + break; > > > > + default: > > > > + cru->input_is_yuv = false; > > > > + icnmc = ICnMC_INF_USER; > > > > + break; > > > > + } > > > > + > > > > + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); > > > > + > > > > + /* Set virtual channel CSI2 */ > > > > + icnmc |= ICnMC_VCSEL(cru->csi.channel); > > > > + > > > > + rzg2l_cru_write(cru, ICnMC, icnmc); > > > > +} > > > > + > > > > +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru) > > > > +{ > > > > + u32 icndmr; > > > > + > > > > + if (cru->is_csi) > > > > + rzg2l_cru_csi2_setup(cru); > > > > + > > > > + /* Output format */ > > > > + switch (cru->format.pixelformat) { > > > > + case V4L2_PIX_FMT_UYVY: > > > > + icndmr = ICnDMR_YCMODE_UYVY; > > > > + cru->output_is_yuv = true; > > > > + break; > > > > + default: > > > > + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", > > > > + cru->format.pixelformat); > > > > + return -EINVAL; > > > > + } > > > > + > > > > + /* If input and output use same colorspace, do bypass mode */ > > > > + if (cru->output_is_yuv == cru->input_is_yuv) > > > > + rzg2l_cru_write(cru, ICnMC, > > > > + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); > > > > + else > > > > + rzg2l_cru_write(cru, ICnMC, > > > > + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); > > > > + > > > > + /* Set output data format */ > > > > + rzg2l_cru_write(cru, ICnDMR, icndmr); > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) > > > > +{ > > > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > > > + struct media_pipeline *pipe; > > > > + struct v4l2_subdev *sd; > > > > + struct media_pad *pad; > > > > + unsigned long flags; > > > > + int ret; > > > > + > > > > + pad = media_pad_remote_pad_first(&cru->pad); > > > > + if (!pad) > > > > + return -EPIPE; > > > > + > > > > + sd = media_entity_to_v4l2_subdev(pad->entity); > > > > + > > > > + if (!on) { > > > > + media_pipeline_stop(&cru->vdev.entity); > > > > + return v4l2_subdev_call(sd, video, s_stream, 0); > > > > + } > > > > + > > > > + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + rzg2l_csi2_dphy_setting(csi2, 1); > > > > + > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > + > > > > + /* Select a video input */ > > > > + if (cru->is_csi) > > > > + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); > > > > + > > > > + /* Cancel the software reset for image processing block */ > > > > + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); > > > > + > > > > + /* Disable and clear the interrupt before using */ > > > > + rzg2l_cru_write(cru, CRUnIE, 0); > > > > + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); > > > > + > > > > + /* Initialize the AXI master */ > > > > + rzg2l_cru_initialize_axi(cru); > > > > + > > > > + /* Initialize image convert */ > > > > + ret = rzg2l_cru_initialize_image_conv(cru); > > > > + if (ret) { > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > + return ret; > > > > + } > > > > + > > > > + /* Enable interrupt */ > > > > + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); > > > > + > > > > + /* Enable image processing reception */ > > > > + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); > > > > + > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > + > > > > + pipe = sd->entity.pipe ? sd->entity.pipe : &cru->vdev.pipe; > > > > + ret = media_pipeline_start(&cru->vdev.entity, pipe); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + clk_disable_unprepare(cru->vclk); > > > > + > > > > + rzg2l_csi2_mipi_link_setting(csi2, 1); > > > > + > > > > + ret = clk_prepare_enable(cru->vclk); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + rzg2l_csi2_cmn_rstb_deassert(csi2); > > > > + > > > > + ret = v4l2_subdev_call(sd, video, s_stream, 1); > > > > + if (ret == -ENOIOCTLCMD) > > > > + ret = 0; > > > > + if (ret) > > > > + media_pipeline_stop(&cru->vdev.entity); > > > > + > > > > + return ret; > > > > +} > > > > + > > > > +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) > > > > +{ > > > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > > > + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; > > > > + unsigned int retries = 0; > > > > + unsigned long flags; > > > > + u32 icnms; > > > > + > > > > + cru->state = RZG2L_CRU_DMA_STOPPING; > > > > + > > > > + rzg2l_cru_set_stream(cru, 0); > > > > + > > > > + rzg2l_csi2_dphy_setting(csi2, 0); > > > > + > > > > + rzg2l_csi2_mipi_link_setting(csi2, 0); > > > > + > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > + > > > > + /* Disable and clear the interrupt */ > > > > + rzg2l_cru_write(cru, CRUnIE, 0); > > > > + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); > > > > + > > > > + /* Stop the operation of image conversion */ > > > > + rzg2l_cru_write(cru, ICnEN, 0); > > > > + > > > > + /* Wait for streaming to stop */ > > > > + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > + msleep(RZG2L_TIMEOUT_MS); > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > + } > > > > + > > > > + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; > > > > + if (icnms) > > > > + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); > > > > + > > > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > > > + > > > > + /* Wait until the FIFO becomes empty */ > > > > + for (retries = 5; retries > 0; retries--) { > > > > + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); > > > > + > > > > + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; > > > > + amnfifopntr_r_y = > > > > + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; > > > > + if (amnfifopntr_w == amnfifopntr_r_y) > > > > + break; > > > > + > > > > + usleep_range(10, 20); > > > > + } > > > > + > > > > + /* Notify that FIFO is not empty here */ > > > > + if (!retries) > > > > + dev_err(cru->dev, "Failed to empty FIFO\n"); > > > > + > > > > + /* Stop AXI bus */ > > > > + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); > > > > + > > > > + /* Wait until the AXI bus stop */ > > > > + for (retries = 5; retries > 0; retries--) { > > > > + if (rzg2l_cru_read(cru, AMnAXISTPACK) & > > > > + AMnAXISTPACK_AXI_STOP_ACK) > > > > + break; > > > > + > > > > + usleep_range(10, 20); > > > > + }; > > > > + > > > > + /* Notify that AXI bus can not stop here */ > > > > + if (!retries) > > > > + dev_err(cru->dev, "Failed to stop AXI bus\n"); > > > > + > > > > + /* Cancel the AXI bus stop request */ > > > > + rzg2l_cru_write(cru, AMnAXISTP, 0); > > > > + > > > > + /* Resets the image processing module */ > > > > + rzg2l_cru_write(cru, CRUnRST, 0); > > > > + > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > + > > > > + /* Set reset state */ > > > > + reset_control_assert(cru->aresetn); > > > > +} > > > > + > > > > +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > > + int ret; > > > > + > > > > + /* Release reset state */ > > > > + ret = reset_control_deassert(cru->aresetn); > > > > + if (ret) { > > > > + dev_err(cru->dev, "failed to deassert aresetn\n"); > > > > + return ret; > > > > + } > > > > + > > > > + /* Allocate scratch buffer. */ > > > > + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, > > > > + &cru->scratch_phys, GFP_KERNEL); > > > > + if (!cru->scratch) { > > > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > > > + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); > > > > + return -ENOMEM; > > > > + } > > > > + > > > > + cru->sequence = 0; > > > > + > > > > + ret = rzg2l_cru_set_stream(cru, 1); > > > > + if (ret) { > > > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > > > + goto out; > > > > + } > > > > + > > > > + cru->state = RZG2L_CRU_DMA_STARTING; > > > > + > > > > + dev_dbg(cru->dev, "Starting to capture\n"); > > > > + > > > > +out: > > > > + if (ret) > > > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > > > + cru->scratch_phys); > > > > + > > > > + return ret; > > > > +} > > > > + > > > > +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > > + > > > > + rzg2l_cru_stop_streaming(cru); > > > > + > > > > + /* Free scratch buffer */ > > > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > > > + cru->scratch_phys); > > > > + > > > > + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); > > > > +} > > > > + > > > > +static const struct vb2_ops rzg2l_cru_qops = { > > > > + .queue_setup = rzg2l_cru_queue_setup, > > > > + .buf_prepare = rzg2l_cru_buffer_prepare, > > > > + .buf_queue = rzg2l_cru_buffer_queue, > > > > + .start_streaming = rzg2l_cru_start_streaming_vq, > > > > + .stop_streaming = rzg2l_cru_stop_streaming_vq, > > > > + .wait_prepare = vb2_ops_wait_prepare, > > > > + .wait_finish = vb2_ops_wait_finish, > > > > +}; > > > > + > > > > +static irqreturn_t rzg2l_cru_irq(int irq, void *data) > > > > +{ > > > > + struct rzg2l_cru_dev *cru = data; > > > > + unsigned int handled = 0; > > > > + unsigned long flags; > > > > + u32 irq_status; > > > > + u32 amnmbs; > > > > + int slot; > > > > + > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > + > > > > + irq_status = rzg2l_cru_read(cru, CRUnINTS); > > > > + if (!irq_status) > > > > + goto done; > > > > + > > > > + handled = 1; > > > > + > > > > + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); > > > > + > > > > + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ > > > > + if (cru->state == RZG2L_CRU_DMA_STOPPED) { > > > > + dev_dbg(cru->dev, "IRQ while state stopped\n"); > > > > + goto done; > > > > + } > > > > + > > > > + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ > > > > + if (cru->state == RZG2L_CRU_DMA_STOPPING) { > > > > + if (irq_status & CRUnINTS_SFS) > > > > + dev_dbg(cru->dev, "IRQ while state stopping\n"); > > > > + goto done; > > > > + } > > > > + > > > > + /* Prepare for capture and update state */ > > > > + amnmbs = rzg2l_cru_read(cru, AMnMBS); > > > > + slot = amnmbs & AMnMBS_MBSTS; > > > > + > > > > + /* > > > > + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). > > > > + * Recalculate to get the current transfer complete MB. > > > > + */ > > > > + if (slot == 0) > > > > + slot = cru->num_buf - 1; > > > > + else > > > > + slot--; > > > > + > > > > + /* > > > > + * To hand buffers back in a known order to userspace start > > > > + * to capture first from slot 0. > > > > + */ > > > > + if (cru->state == RZG2L_CRU_DMA_STARTING) { > > > > + if (slot != 0) { > > > > + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); > > > > + goto done; > > > > + } > > > > + > > > > + dev_dbg(cru->dev, "Capture start synced!\n"); > > > > + cru->state = RZG2L_CRU_DMA_RUNNING; > > > > + } > > > > + > > > > + /* Capture frame */ > > > > + if (cru->queue_buf[slot]) { > > > > + cru->queue_buf[slot]->field = cru->format.field; > > > > + cru->queue_buf[slot]->sequence = cru->sequence; > > > > + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); > > > > + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, > > > > + VB2_BUF_STATE_DONE); > > > > + cru->queue_buf[slot] = NULL; > > > > + } else { > > > > + /* Scratch buffer was used, dropping frame. */ > > > > + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); > > > > + } > > > > + > > > > + cru->sequence++; > > > > + > > > > + /* Prepare for next frame */ > > > > + rzg2l_cru_fill_hw_slot(cru, slot); > > > > + > > > > +done: > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > + > > > > + return IRQ_RETVAL(handled); > > > > +} > > > > + > > > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) > > > > +{ > > > > + mutex_destroy(&cru->lock); > > > > + > > > > + v4l2_device_unregister(&cru->v4l2_dev); > > > > + reset_control_assert(cru->presetn); > > > > +} > > > > + > > > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq) > > > > +{ > > > > + struct vb2_queue *q = &cru->queue; > > > > + unsigned int i; > > > > + int ret; > > > > + > > > > + ret = reset_control_deassert(cru->presetn); > > > > + if (ret) { > > > > + dev_err(cru->dev, "failed to deassert presetn\n"); > > > > + return ret; > > > > + } > > > > > > Shouldn't this be done when starting streaming instead ? > > > > Agreed, Ill move it there. > > > > > > + > > > > + /* Initialize the top-level structure */ > > > > + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + mutex_init(&cru->lock); > > > > + INIT_LIST_HEAD(&cru->buf_list); > > > > + > > > > + spin_lock_init(&cru->qlock); > > > > + > > > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > > > + > > > > + for (i = 0; i < HW_BUFFER_MAX; i++) > > > > + cru->queue_buf[i] = NULL; > > > > + > > > > + /* buffer queue */ > > > > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > > > > + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; > > > > > > No VB2_READ please, that's very inefficient. > > > > OK, I'll drop it. > > > > > > + q->lock = &cru->lock; > > > > + q->drv_priv = cru; > > > > + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); > > > > + q->ops = &rzg2l_cru_qops; > > > > + q->mem_ops = &vb2_dma_contig_memops; > > > > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > > > > + q->min_buffers_needed = 4; > > > > > > Does the hardware really require 4 buffers to operate ? > > > > v4l2-compliance complains about sequnce mismatch when set to 3. > > Then maybe there's another problem somewhere ? What's the minimum number > of buffers the hardware can operate with ? > FIFO depth is 16. We are using the similar code as rcar-vin where we use the scratch buffer (and also the rcar-vin sets the min_buffers_needed to 4) Cheers, Prabhakar
Hi Prabhakar, On Mon, Sep 26, 2022 at 05:24:47PM +0100, Lad, Prabhakar wrote: > On Mon, Sep 26, 2022 at 9:59 AM Laurent Pinchart wrote: > > On Fri, Sep 23, 2022 at 08:02:12PM +0100, Lad, Prabhakar wrote: > > > On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart wrote: > > > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > > > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > > > > > > > Based on a patch in the BSP by Hien Huynh > > > > > <hien.huynh.px@renesas.com> > > > > > > > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > > > --- > > > > > v1 -> v2 > > > > > * No change > > > > > > > > > > RFC v2 -> v1 > > > > > * Moved the driver to renesas folder > > > > > * Fixed review comments pointed by Jacopo > > > > > > > > > > RFC v1 -> RFC v2 > > > > > * Dropped group > > > > > * Dropped CSI subdev and implemented as new driver > > > > > * Dropped "mc_" from function names > > > > > * Moved the driver to renesas folder > > > > > --- > > > > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > > > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > > > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > > > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > > > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > > > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > > > > > > > I'd merge those two files together, they both handle the video node. > > > > There's a comment below that recommends adding a subdev, that should > > > > then go to a separate file. > > > > > > OK, I'll merge these files into rzg2l-video.c. > > > > > > > > 6 files changed, 1669 insertions(+) > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > index 57c40bb499df..08ff0e96b3f5 100644 > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > > > > > > > To compile this driver as a module, choose M here: the > > > > > module will be called rzg2l-csi2. > > > > > + > > > > > +config VIDEO_RZG2L_CRU > > > > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > > > + depends on V4L_PLATFORM_DRIVERS > > > > > + depends on VIDEO_DEV && OF > > > > > + select MEDIA_CONTROLLER > > > > > + select V4L2_FWNODE > > > > > + select VIDEOBUF2_DMA_CONTIG > > > > > + select VIDEO_RZG2L_CSI2 > > > > > > > > Is this required, can't the CRU be used with a parallel sensor without > > > > the CSI-2 receiver ? > > > > > > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > > > > > > > + select VIDEO_V4L2_SUBDEV_API > > > > > + help > > > > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > > > > + Unit (CRU) driver. > > > > > + > > > > > + To compile this driver as a module, choose M here: the > > > > > + module will be called rzg2l-cru. > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > index 91ea97a944e6..7628809e953f 100644 > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > @@ -1,3 +1,6 @@ > > > > > # SPDX-License-Identifier: GPL-2.0 > > > > > > > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > > > > + > > > > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > > > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > new file mode 100644 > > > > > index 000000000000..b5d4110b1913 > > > > > --- /dev/null > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > @@ -0,0 +1,395 @@ > > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > > +/* > > > > > + * Driver for Renesas RZ/G2L CRU > > > > > + * > > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > > + * > > > > > + * Based on Renesas R-Car VIN > > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > > + * Copyright (C) 2008 Magnus Damm > > > > > + */ > > > > > + > > > > > +#include <linux/clk.h> > > > > > +#include <linux/module.h> > > > > > +#include <linux/mod_devicetable.h> > > > > > +#include <linux/of.h> > > > > > +#include <linux/of_device.h> > > > > > +#include <linux/of_graph.h> > > > > > +#include <linux/platform_device.h> > > > > > +#include <linux/pm_runtime.h> > > > > > + > > > > > +#include <media/v4l2-fwnode.h> > > > > > +#include <media/v4l2-mc.h> > > > > > + > > > > > +#include "rzg2l-cru.h" > > > > > + > > > > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > > > > > > > As this macro is only used to get the rzg2l_cru_dev pointer from the > > > > v4l2_async_notifier pointer, you can replace it with > > > > > > > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > > > > > > > I would also turn it into a static inline function for additional > > > > compile-time type safety. > > > > > > OK, I will do it as mentioned above. > > > > > > > > + > > > > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > > > > + unsigned int notification) > > > > > +{ > > > > > + struct media_entity *entity; > > > > > + struct rzg2l_cru_dev *cru; > > > > > + struct media_pad *csi_pad; > > > > > + struct v4l2_subdev *sd; > > > > > + int ret; > > > > > + > > > > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + /* Only care about link enablement for CRU nodes. */ > > > > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > > > > + return 0; > > > > > + > > > > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > > > > + /* > > > > > + * Don't allow link changes if any entity in the graph is > > > > > + * streaming, modifying the CHSEL register fields can disrupt > > > > > + * running streams. > > > > > + */ > > > > > + media_device_for_each_entity(entity, &cru->mdev) > > > > > + if (media_entity_is_streaming(entity)) > > > > > + return -EBUSY; > > > > > + > > > > > + mutex_lock(&cru->mdev_lock); > > > > > + > > > > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > > > > + if (csi_pad) { > > > > > + ret = -EMLINK; > > > > > + goto out; > > > > > + } > > > > > + > > > > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > > > > + if (cru->csi.subdev == sd) { > > > > > + cru->csi.channel = link->source->index - 1; > > > > > + cru->is_csi = true; > > > > > + } else { > > > > > + ret = -ENODEV; > > > > > + } > > > > > + > > > > > +out: > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > + > > > > > + return ret; > > > > > +} > > > > > + > > > > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > > > > + .link_notify = rzg2l_cru_csi2_link_notify, > > > > > +}; > > > > > + > > > > > +/* ----------------------------------------------------------------------------- > > > > > + * Group async notifier > > > > > + */ > > > > > + > > > > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > > + unsigned int i; > > > > > + int ret; > > > > > + > > > > > + ret = media_device_register(&cru->mdev); > > > > > + if (ret) > > > > > + return ret; > > > > > > > > I'd move the v4l2_device_register() call here, as it's the V4L2 > > > > counterpart of the media device, and handling them together would be > > > > best. > > > > > > OK. > > > > > > > > + > > > > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > > > > + if (ret) { > > > > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > > > > + return ret; > > > > > + } > > > > > + > > > > > + if (!video_is_registered(&cru->vdev)) { > > > > > > > > Can this happen ? > > > > > > No, I'll drop this check. > > > > > > > > + ret = rzg2l_cru_v4l2_register(cru); > > > > > + if (ret) > > > > > + return ret; > > > > > + } > > > > > + > > > > > + /* Create all media device links between CRU and CSI-2's. */ > > > > > + /* > > > > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > > > > + * should be implemented by streams API which is under development > > > > > + * so for now just link it to VC0 > > > > > + */ > > > > > > > > The streams API won't require more links, so I'd drop the comment and > > > > the loop and create a single link. > > > > > > OK. > > > > > > > > + for (i = 1; i <= 1; i++) { > > > > > + struct media_entity *source, *sink; > > > > > + > > > > > + source = &cru->csi.subdev->entity; > > > > > + sink = &cru->vdev.entity; > > > > > > > > Hmmm... I'd recommend adding a subdev to model the image processing > > > > pipeline of the CRU, between the CSI-2 receiver and the video node. That > > > > will help when you'll add support for parallel sensors, and it will also > > > > be needed by the streams API to select which virtual channel to capture. > > >/ > > > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? > > > > I think MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER would be more > > appropriate. > > OK I will use MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER. As this will be > just like a switch should I be implementing the get_fmt/set_fmt > callbacks? Yes, subdev operations need to be implemented, especially given that the CRU implements color space conversion, so the input and output formats of the subdev can be different. > > > > > + > > > > > + ret = media_create_pad_link(source, i, sink, 0, 0); > > > > > + if (ret) { > > > > > + dev_err(cru->dev, "Error adding link from %s to %s\n", > > > > > + source->name, sink->name); > > > > > + break; > > > > > + } > > > > > + } > > > > > + > > > > > + return ret; > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, > > > > > + struct v4l2_subdev *subdev, > > > > > + struct v4l2_async_subdev *asd) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > > + > > > > > + rzg2l_cru_v4l2_unregister(cru); > > > > > + > > > > > + mutex_lock(&cru->mdev_lock); > > > > > + > > > > > + if (cru->csi.asd == asd) { > > > > > + cru->csi.subdev = NULL; > > > > > + dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); > > > > > + } > > > > > + > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > + > > > > > + media_device_unregister(&cru->mdev); > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, > > > > > + struct v4l2_subdev *subdev, > > > > > + struct v4l2_async_subdev *asd) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > > + > > > > > + mutex_lock(&cru->mdev_lock); > > > > > + > > > > > + if (cru->csi.asd == asd) { > > > > > + cru->csi.subdev = subdev; > > > > > + dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); > > > > > + } > > > > > + > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { > > > > > + .bound = rzg2l_cru_group_notify_bound, > > > > > + .unbind = rzg2l_cru_group_notify_unbind, > > > > > + .complete = rzg2l_cru_group_notify_complete, > > > > > +}; > > > > > + > > > > > +static int rvin_mc_parse_of(struct rzg2l_cru_dev *cru, unsigned int id) > > > > > > > > The id parameter is always 0, I'd drop it. > > > > > > Agreed, I will drop it. > > > > > > > > +{ > > > > > + struct v4l2_fwnode_endpoint vep = { > > > > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > > > > + }; > > > > > + struct fwnode_handle *ep, *fwnode; > > > > > + struct v4l2_async_subdev *asd; > > > > > + int ret; > > > > > + > > > > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, id, 0); > > > > > + if (!ep) > > > > > + return 0; > > > > > + > > > > > + fwnode = fwnode_graph_get_remote_endpoint(ep); > > > > > + ret = v4l2_fwnode_endpoint_parse(ep, &vep); > > > > > + fwnode_handle_put(ep); > > > > > + if (ret) { > > > > > + dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); > > > > > + ret = -EINVAL; > > > > > + goto out; > > > > > + } > > > > > + > > > > > + if (!of_device_is_available(to_of_node(fwnode))) { > > > > > + dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", > > > > > + to_of_node(fwnode)); > > > > > + ret = -ENOTCONN; > > > > > + goto out; > > > > > + } > > > > > + > > > > > + asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, > > > > > + struct v4l2_async_subdev); > > > > > + if (IS_ERR(asd)) { > > > > > + ret = PTR_ERR(asd); > > > > > + goto out; > > > > > + } > > > > > + > > > > > + cru->csi.asd = asd; > > > > > + > > > > > + dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", > > > > > + to_of_node(fwnode), vep.base.id); > > > > > +out: > > > > > + fwnode_handle_put(fwnode); > > > > > + > > > > > + return ret; > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) > > > > > +{ > > > > > + int ret; > > > > > + > > > > > + v4l2_async_nf_init(&cru->notifier); > > > > > + > > > > > + ret = rvin_mc_parse_of(cru, 0); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + cru->notifier.ops = &rzg2l_cru_async_ops; > > > > > + > > > > > + if (list_empty(&cru->notifier.asd_list)) > > > > > + return 0; > > > > > + > > > > > + ret = v4l2_async_nf_register(&cru->v4l2_dev, &cru->notifier); > > > > > + if (ret < 0) { > > > > > + dev_err(cru->dev, "Notifier registration failed\n"); > > > > > + v4l2_async_nf_cleanup(&cru->notifier); > > > > > + return ret; > > > > > + } > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_csi2_init(struct rzg2l_cru_dev *cru) > > > > > > > > The naming is a bit weird, as this isn't related to CSI-2. I would name > > > > the function rzg2l_cru_media_init(). > > > > > > Agreed, I will rename it. > > > > > > > > +{ > > > > > + struct media_device *mdev = NULL; > > > > > + const struct of_device_id *match; > > > > > + int ret; > > > > > + > > > > > + cru->pad.flags = MEDIA_PAD_FL_SINK; > > > > > + ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + mutex_init(&cru->mdev_lock); > > > > > + mdev = &cru->mdev; > > > > > + mdev->dev = cru->dev; > > > > > + mdev->ops = &rzg2l_cru_media_ops; > > > > > + > > > > > + match = of_match_node(cru->dev->driver->of_match_table, > > > > > + cru->dev->of_node); > > > > > + > > > > > + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); > > > > > + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); > > > > > + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", > > > > > + dev_name(mdev->dev)); > > > > > + > > > > > + cru->v4l2_dev.mdev = &cru->mdev; > > > > > + > > > > > + media_device_init(mdev); > > > > > + > > > > > + ret = rzg2l_cru_mc_parse_of_graph(cru); > > > > > + if (ret) { > > > > > + mutex_lock(&cru->mdev_lock); > > > > > + cru->v4l2_dev.mdev = NULL; > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > + } > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_probe(struct platform_device *pdev) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru; > > > > > + int irq, ret; > > > > > + > > > > > + cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); > > > > > + if (!cru) > > > > > + return -ENOMEM; > > > > > + > > > > > + cru->base = devm_platform_ioremap_resource(pdev, 0); > > > > > + if (IS_ERR(cru->base)) > > > > > + return PTR_ERR(cru->base); > > > > > + > > > > > + cru->presetn = devm_reset_control_get(&pdev->dev, "presetn"); > > > > > + if (IS_ERR(cru->presetn)) > > > > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), > > > > > + "failed to get cpg presetn\n"); > > > > > + > > > > > + cru->aresetn = devm_reset_control_get(&pdev->dev, "aresetn"); > > > > > + if (IS_ERR(cru->aresetn)) > > > > > + return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), > > > > > + "failed to get cpg aresetn\n"); > > > > > + > > > > > + cru->vclk = devm_clk_get(&pdev->dev, "vclk"); > > > > > + if (IS_ERR(cru->vclk)) { > > > > > + dev_err(&pdev->dev, "Failed to get vclk"); > > > > > + return PTR_ERR(cru->vclk); > > > > > > > > You could use dev_err_probe() here too (as well as below). > > > > > > OK. > > > > > > > > + } > > > > > + > > > > > + cru->pclk = devm_clk_get(&pdev->dev, "pclk"); > > > > > + if (IS_ERR(cru->pclk)) { > > > > > + dev_err(&pdev->dev, "Failed to get pclk"); > > > > > + return PTR_ERR(cru->pclk); > > > > > + } > > > > > + > > > > > + cru->aclk = devm_clk_get(&pdev->dev, "aclk"); > > > > > + if (IS_ERR(cru->aclk)) { > > > > > + dev_err(&pdev->dev, "Failed to get aclk"); > > > > > + return PTR_ERR(cru->aclk); > > > > > + } > > > > > + > > > > > + cru->dev = &pdev->dev; > > > > > + cru->info = of_device_get_match_data(&pdev->dev); > > > > > + > > > > > + irq = platform_get_irq(pdev, 0); > > > > > + if (irq < 0) > > > > > + return irq; > > > > > + > > > > > + ret = rzg2l_cru_dma_register(cru, irq); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + platform_set_drvdata(pdev, cru); > > > > > + > > > > > + ret = rzg2l_cru_csi2_init(cru); > > > > > + if (ret) > > > > > + goto error_dma_unregister; > > > > > + > > > > > + cru->num_buf = HW_BUFFER_DEFAULT; > > > > > + pm_suspend_ignore_children(&pdev->dev, true); > > > > > + pm_runtime_enable(&pdev->dev); > > > > > + > > > > > + return 0; > > > > > + > > > > > +error_dma_unregister: > > > > > + rzg2l_cru_dma_unregister(cru); > > > > > + > > > > > + return ret; > > > > > +} > > > > > + > > > > > +static const struct of_device_id rzg2l_cru_of_id_table[] = { > > > > > + { > > > > > + .compatible = "renesas,rzg2l-cru", > > > > > + }, > > > > > + { /* sentinel */ } > > > > > +}; > > > > > +MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); > > > > > + > > > > > +static int rzg2l_cru_remove(struct platform_device *pdev) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); > > > > > + > > > > > + pm_runtime_disable(&pdev->dev); > > > > > + > > > > > + rzg2l_cru_v4l2_unregister(cru); > > > > > + > > > > > + v4l2_async_nf_unregister(&cru->notifier); > > > > > + v4l2_async_nf_cleanup(&cru->notifier); > > > > > + > > > > > + media_device_cleanup(&cru->mdev); > > > > > + mutex_destroy(&cru->mdev_lock); > > > > > + cru->v4l2_dev.mdev = NULL; > > > > > > > > Is this needed ? > > > > > > Not required. > > > > > > > > + > > > > > + rzg2l_cru_dma_unregister(cru); > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static struct platform_driver rzg2l_cru_driver = { > > > > > + .driver = { > > > > > + .name = "rzg2l-cru", > > > > > + .of_match_table = rzg2l_cru_of_id_table, > > > > > + }, > > > > > + .probe = rzg2l_cru_probe, > > > > > + .remove = rzg2l_cru_remove, > > > > > > > > No PM ? > > > > > > I plan to gradually add at a later point. > > > > > > > > +}; > > > > > + > > > > > +module_platform_driver(rzg2l_cru_driver); > > > > > + > > > > > +MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); > > > > > +MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); > > > > > +MODULE_LICENSE("GPL"); > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > > new file mode 100644 > > > > > index 000000000000..a834680a3200 > > > > > --- /dev/null > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > > @@ -0,0 +1,152 @@ > > > > > +/* SPDX-License-Identifier: GPL-2.0+ */ > > > > > +/* > > > > > + * Driver for Renesas RZ/G2L CRU > > > > > + * > > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > > + * > > > > > > > > Extra blank line. > > > > > > Oops, I will drop it. > > > > > > > > + */ > > > > > + > > > > > +#ifndef __RZG2L_CRU__ > > > > > +#define __RZG2L_CRU__ > > > > > + > > > > > +#include <linux/reset.h> > > > > > + > > > > > +#include <media/v4l2-async.h> > > > > > +#include <media/v4l2-ctrls.h> > > > > > +#include <media/v4l2-dev.h> > > > > > +#include <media/v4l2-device.h> > > > > > +#include <media/videobuf2-v4l2.h> > > > > > + > > > > > +/* Number of HW buffers */ > > > > > +#define HW_BUFFER_MAX 8 > > > > > +#define HW_BUFFER_DEFAULT 3 > > > > > > > > Could you prefix macro names with CRU_ (or RZG2L_CRU_, up to you) ? > > > > These names are a bit generic and could lead to clashes. > > > > > > Agreed, I will rename it. > > > > > > > > + > > > > > +/* Address alignment mask for HW buffers */ > > > > > +#define HW_BUFFER_MASK 0x1ff > > > > > + > > > > > +/* Maximum number of CSI2 virtual channels */ > > > > > +#define CSI2_VCHANNEL 4 > > > > > + > > > > > +#define CRU_MAX_INPUT_WIDTH 2800 > > > > > +#define CRU_MAX_INPUT_HEIGHT 4095 > > > > > + > > > > > +/** > > > > > + * enum rzg2l_cru_dma_state - DMA states > > > > > + * @RZG2L_CRU_DMA_STOPPED: No operation in progress > > > > > + * @RZG2L_CRU_DMA_STARTING: Capture starting up > > > > > + * @RZG2L_CRU_DMA_RUNNING: Operation in progress have buffers > > > > > + * @RZG2L_CRU_DMA_STOPPING: Stopping operation > > > > > + */ > > > > > +enum rzg2l_cru_dma_state { > > > > > + RZG2L_CRU_DMA_STOPPED = 0, > > > > > + RZG2L_CRU_DMA_STARTING, > > > > > + RZG2L_CRU_DMA_RUNNING, > > > > > + RZG2L_CRU_DMA_STOPPING, > > > > > +}; > > > > > + > > > > > +struct rzg2l_cru_csi { > > > > > + struct v4l2_async_subdev *asd; > > > > > + struct v4l2_subdev *subdev; > > > > > + u32 channel; > > > > > +}; > > > > > + > > > > > +/** > > > > > + * struct rzg2l_cru_dev - Renesas CRU device structure > > > > > + * @dev: (OF) device > > > > > + * @base: device I/O register space remapped to virtual memory > > > > > + * @info: info about CRU instance > > > > > + * > > > > > + * @presetn: CRU_PRESETN reset line > > > > > + * @aresetn: CRU_ARESETN reset line > > > > > + * > > > > > + * @vclk: CRU Main clock > > > > > + * @pclk: CPU Register access clock > > > > > + * @aclk: CRU image transfer clock > > > > > + * > > > > > + * @vdev: V4L2 video device associated with CRU > > > > > + * @v4l2_dev: V4L2 device > > > > > + * @ctrl_handler: V4L2 control handler > > > > > + * @num_buf: Holds the current number of buffers enabled > > > > > + * @notifier: V4L2 asynchronous subdevs notifier > > > > > + * > > > > > + * @csi: CSI info > > > > > + * @mdev: media device > > > > > + * @mdev_lock: protects the count, notifier and csi members > > > > > + * @pad: media pad for the video device entity > > > > > + * > > > > > + * @lock: protects @queue > > > > > + * @queue: vb2 buffers queue > > > > > + * @scratch: cpu address for scratch buffer > > > > > + * @scratch_phys: physical address of the scratch buffer > > > > > + * > > > > > + * @qlock: protects @queue_buf, @buf_list, @sequence > > > > > + * @state > > > > > + * @queue_buf: Keeps track of buffers given to HW slot > > > > > + * @buf_list: list of queued buffers > > > > > + * @sequence: V4L2 buffers sequence number > > > > > + * @state: keeps track of operation state > > > > > + * > > > > > + * @is_csi: flag to mark the CRU as using a CSI-2 subdevice > > > > > + * > > > > > + * @input_is_yuv: flag to mark the input format of CRU > > > > > + * @output_is_yuv: flag to mark the output format of CRU > > > > > + * > > > > > + * @mbus_code: media bus format code > > > > > + * @format: active V4L2 pixel format > > > > > + * > > > > > + * @compose: active composing > > > > > + */ > > > > > +struct rzg2l_cru_dev { > > > > > + struct device *dev; > > > > > + void __iomem *base; > > > > > + const struct rzg2l_cru_info *info; > > > > > + > > > > > + struct reset_control *presetn; > > > > > + struct reset_control *aresetn; > > > > > + > > > > > + struct clk *vclk; > > > > > + struct clk *pclk; > > > > > + struct clk *aclk; > > > > > + > > > > > + struct video_device vdev; > > > > > + struct v4l2_device v4l2_dev; > > > > > + u8 num_buf; > > > > > + > > > > > + struct v4l2_async_notifier notifier; > > > > > + > > > > > + struct rzg2l_cru_csi csi; > > > > > + struct media_device mdev; > > > > > + struct mutex mdev_lock; > > > > > + struct media_pad pad; > > > > > + > > > > > + struct mutex lock; > > > > > + struct vb2_queue queue; > > > > > + void *scratch; > > > > > + dma_addr_t scratch_phys; > > > > > + > > > > > + spinlock_t qlock; > > > > > + struct vb2_v4l2_buffer *queue_buf[HW_BUFFER_MAX]; > > > > > + struct list_head buf_list; > > > > > + unsigned int sequence; > > > > > + enum rzg2l_cru_dma_state state; > > > > > + > > > > > + bool is_csi; > > > > > + > > > > > + bool input_is_yuv; > > > > > + bool output_is_yuv; > > > > > + > > > > > + u32 mbus_code; > > > > > + struct v4l2_pix_format format; > > > > > + > > > > > + struct v4l2_rect compose; > > > > > +}; > > > > > + > > > > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq); > > > > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru); > > > > > + > > > > > +int rzg2l_cru_v4l2_register(struct rzg2l_cru_dev *cru); > > > > > +void rzg2l_cru_v4l2_unregister(struct rzg2l_cru_dev *cru); > > > > > + > > > > > +const struct v4l2_format_info *rzg2l_cru_format_from_pixel(u32 format); > > > > > + > > > > > +#endif > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > > new file mode 100644 > > > > > index 000000000000..44efd071f562 > > > > > --- /dev/null > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > > @@ -0,0 +1,734 @@ > > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > > +/* > > > > > + * Driver for Renesas RZ/G2L CRU > > > > > + * > > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > > + * > > > > > + * Based on Renesas R-Car VIN > > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > > + * Copyright (C) 2008 Magnus Damm > > > > > + */ > > > > > + > > > > > +#include <linux/clk.h> > > > > > +#include <linux/delay.h> > > > > > +#include <linux/interrupt.h> > > > > > + > > > > > +#include <media/videobuf2-dma-contig.h> > > > > > + > > > > > +#include "rzg2l-cru.h" > > > > > +#include "rzg2l-csi2.h" > > > > > + > > > > > +/* HW CRU Registers Definition */ > > > > > +/* CRU Control Register */ > > > > > +#define CRUnCTRL 0x0 > > > > > +#define CRUnCTRL_VINSEL(x) ((x) << 0) > > > > > + > > > > > +/* CRU Interrupt Enable Register */ > > > > > +#define CRUnIE 0x4 > > > > > +#define CRUnIE_SFE BIT(16) > > > > > +#define CRUnIE_EFE BIT(17) > > > > > + > > > > > +/* CRU Interrupt Status Register */ > > > > > +#define CRUnINTS 0x8 > > > > > +#define CRUnINTS_SFS BIT(16) > > > > > + > > > > > +/* CRU Reset Register */ > > > > > +#define CRUnRST 0xc > > > > > +#define CRUnRST_VRESETN BIT(0) > > > > > + > > > > > +/* Memory Bank Base Address (Lower) Register for CRU Image Data */ > > > > > +#define AMnMBxADDRL(x) (0x100 + ((x) * 8)) > > > > > + > > > > > +/* Memory Bank Base Address (Higher) Register for CRU Image Data */ > > > > > +#define AMnMBxADDRH(x) (0x104 + ((x) * 8)) > > > > > + > > > > > +/* Memory Bank Enable Register for CRU Image Data */ > > > > > +#define AMnMBVALID 0x148 > > > > > +#define AMnMBVALID_MBVALID(x) GENMASK(x, 0) > > > > > + > > > > > +/* Memory Bank Status Register for CRU Image Data */ > > > > > +#define AMnMBS 0x14c > > > > > +#define AMnMBS_MBSTS 0x7 > > > > > + > > > > > +/* AXI Master FIFO Pointer Register for CRU Image Data */ > > > > > +#define AMnFIFOPNTR 0x168 > > > > > +#define AMnFIFOPNTR_FIFOWPNTR GENMASK(7, 0) > > > > > +#define AMnFIFOPNTR_FIFORPNTR_Y GENMASK(23, 16) > > > > > + > > > > > +/* AXI Master Transfer Stop Register for CRU Image Data */ > > > > > +#define AMnAXISTP 0x174 > > > > > +#define AMnAXISTP_AXI_STOP BIT(0) > > > > > + > > > > > +/* AXI Master Transfer Stop Status Register for CRU Image Data */ > > > > > +#define AMnAXISTPACK 0x178 > > > > > +#define AMnAXISTPACK_AXI_STOP_ACK BIT(0) > > > > > + > > > > > +/* CRU Image Processing Enable Register */ > > > > > +#define ICnEN 0x200 > > > > > +#define ICnEN_ICEN BIT(0) > > > > > + > > > > > +/* CRU Image Processing Main Control Register */ > > > > > +#define ICnMC 0x208 > > > > > +#define ICnMC_CSCTHR BIT(5) > > > > > +#define ICnMC_INF_YUV8_422 (0x1e << 16) > > > > > +#define ICnMC_INF_USER (0x30 << 16) > > > > > +#define ICnMC_VCSEL(x) ((x) << 22) > > > > > +#define ICnMC_INF_MASK GENMASK(21, 16) > > > > > + > > > > > +/* CRU Module Status Register */ > > > > > +#define ICnMS 0x254 > > > > > +#define ICnMS_IA BIT(2) > > > > > + > > > > > +/* CRU Data Output Mode Register */ > > > > > +#define ICnDMR 0x26c > > > > > +#define ICnDMR_YCMODE_UYVY (1 << 4) > > > > > + > > > > > +#define RZG2L_TIMEOUT_MS 100 > > > > > +#define RZG2L_RETRIES 10 > > > > > + > > > > > +struct rzg2l_cru_buffer { > > > > > + struct vb2_v4l2_buffer vb; > > > > > + struct list_head list; > > > > > +}; > > > > > + > > > > > +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ > > > > > + struct rzg2l_cru_buffer, \ > > > > > + vb)->list) > > > > > + > > > > > +static void rzg2l_cru_write(struct rzg2l_cru_dev *cru, u32 offset, u32 value) > > > > > +{ > > > > > + iowrite32(value, cru->base + offset); > > > > > +} > > > > > + > > > > > +static u32 rzg2l_cru_read(struct rzg2l_cru_dev *cru, u32 offset) > > > > > +{ > > > > > + return ioread32(cru->base + offset); > > > > > +} > > > > > + > > > > > +/* Need to hold qlock before calling */ > > > > > +static void return_unused_buffers(struct rzg2l_cru_dev *cru, > > > > > + enum vb2_buffer_state state) > > > > > +{ > > > > > + struct rzg2l_cru_buffer *buf, *node; > > > > > + unsigned long flags; > > > > > + unsigned int i; > > > > > + > > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > > + for (i = 0; i < cru->num_buf; i++) { > > > > > + if (cru->queue_buf[i]) { > > > > > + vb2_buffer_done(&cru->queue_buf[i]->vb2_buf, > > > > > + state); > > > > > + cru->queue_buf[i] = NULL; > > > > > + } > > > > > + } > > > > > + > > > > > + list_for_each_entry_safe(buf, node, &cru->buf_list, list) { > > > > > + vb2_buffer_done(&buf->vb.vb2_buf, state); > > > > > + list_del(&buf->list); > > > > > + } > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, > > > > > + unsigned int *nplanes, unsigned int sizes[], > > > > > + struct device *alloc_devs[]) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > > > + > > > > > + /* Make sure the image size is large enough. */ > > > > > + if (*nplanes) > > > > > + return sizes[0] < cru->format.sizeimage ? -EINVAL : 0; > > > > > + > > > > > + *nplanes = 1; > > > > > + sizes[0] = cru->format.sizeimage; > > > > > + > > > > > + return 0; > > > > > +}; > > > > > + > > > > > +static int rzg2l_cru_buffer_prepare(struct vb2_buffer *vb) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > > > > + unsigned long size = cru->format.sizeimage; > > > > > + > > > > > + if (vb2_plane_size(vb, 0) < size) { > > > > > + dev_err(cru->dev, "buffer too small (%lu < %lu)\n", > > > > > + vb2_plane_size(vb, 0), size); > > > > > + return -EINVAL; > > > > > + } > > > > > + > > > > > + vb2_set_plane_payload(vb, 0, size); > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_buffer_queue(struct vb2_buffer *vb) > > > > > +{ > > > > > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vb->vb2_queue); > > > > > + unsigned long flags; > > > > > + > > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > > + > > > > > + list_add_tail(to_buf_list(vbuf), &cru->buf_list); > > > > > + > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_mc_validate_format(struct rzg2l_cru_dev *cru, > > > > > + struct v4l2_subdev *sd, > > > > > + struct media_pad *pad) > > > > > +{ > > > > > + struct v4l2_subdev_format fmt = { > > > > > + .which = V4L2_SUBDEV_FORMAT_ACTIVE, > > > > > + }; > > > > > + > > > > > + fmt.pad = pad->index; > > > > > + if (v4l2_subdev_call_state_active(sd, pad, get_fmt, &fmt)) > > > > > + return -EPIPE; > > > > > + > > > > > + if (cru->is_csi) { > > > > > + switch (fmt.format.code) { > > > > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > > > > + break; > > > > > + default: > > > > > + return -EPIPE; > > > > > + } > > > > > + } > > > > > + cru->mbus_code = fmt.format.code; > > > > > + > > > > > + switch (fmt.format.field) { > > > > > + case V4L2_FIELD_TOP: > > > > > + case V4L2_FIELD_BOTTOM: > > > > > + case V4L2_FIELD_NONE: > > > > > + case V4L2_FIELD_INTERLACED_TB: > > > > > + case V4L2_FIELD_INTERLACED_BT: > > > > > + case V4L2_FIELD_INTERLACED: > > > > > + case V4L2_FIELD_SEQ_TB: > > > > > + case V4L2_FIELD_SEQ_BT: > > > > > + break; > > > > > + default: > > > > > + return -EPIPE; > > > > > + } > > > > > + > > > > > + if (fmt.format.width != cru->format.width || > > > > > + fmt.format.height != cru->format.height || > > > > > + fmt.format.code != cru->mbus_code) > > > > > + return -EPIPE; > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_set_slot_addr(struct rzg2l_cru_dev *cru, > > > > > + int slot, dma_addr_t addr) > > > > > +{ > > > > > + const struct v4l2_format_info *fmt; > > > > > + int offsetx, offsety; > > > > > + dma_addr_t offset; > > > > > + > > > > > + fmt = rzg2l_cru_format_from_pixel(cru->format.pixelformat); > > > > > + > > > > > + /* > > > > > + * There is no HW support for composition do the best we can > > > > > + * by modifying the buffer offset > > > > > + */ > > > > > + offsetx = cru->compose.left * fmt->bpp[0]; > > > > > + offsety = cru->compose.top * cru->format.bytesperline; > > > > > + offset = addr + offsetx + offsety; > > > > > + > > > > > + /* > > > > > + * The address needs to be 512 bytes aligned. Driver should never accept > > > > > + * settings that do not satisfy this in the first place... > > > > > + */ > > > > > + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) > > > > > + return; > > > > > + > > > > > + /* Currently, we just use the buffer in 32 bits address */ > > > > > + rzg2l_cru_write(cru, AMnMBxADDRL(slot), offset); > > > > > + rzg2l_cru_write(cru, AMnMBxADDRH(slot), 0); > > > > > +} > > > > > + > > > > > +/* > > > > > + * Moves a buffer from the queue to the HW slot. If no buffer is > > > > > + * available use the scratch buffer. The scratch buffer is never > > > > > + * returned to userspace, its only function is to enable the capture > > > > > + * loop to keep running. > > > > > + */ > > > > > +static void rzg2l_cru_fill_hw_slot(struct rzg2l_cru_dev *cru, int slot) > > > > > +{ > > > > > + struct vb2_v4l2_buffer *vbuf; > > > > > + struct rzg2l_cru_buffer *buf; > > > > > + dma_addr_t phys_addr; > > > > > + > > > > > + /* A already populated slot shall never be overwritten. */ > > > > > + if (WARN_ON(cru->queue_buf[slot])) > > > > > + return; > > > > > + > > > > > + dev_dbg(cru->dev, "Filling HW slot: %d\n", slot); > > > > > + > > > > > + if (list_empty(&cru->buf_list)) { > > > > > + cru->queue_buf[slot] = NULL; > > > > > + phys_addr = cru->scratch_phys; > > > > > + } else { > > > > > + /* Keep track of buffer we give to HW */ > > > > > + buf = list_entry(cru->buf_list.next, > > > > > + struct rzg2l_cru_buffer, list); > > > > > + vbuf = &buf->vb; > > > > > + list_del_init(to_buf_list(vbuf)); > > > > > + cru->queue_buf[slot] = vbuf; > > > > > + > > > > > + /* Setup DMA */ > > > > > + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); > > > > > + } > > > > > + > > > > > + rzg2l_cru_set_slot_addr(cru, slot, phys_addr); > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) > > > > > +{ > > > > > + unsigned int slot; > > > > > + > > > > > + /* > > > > > + * Set image data memory banks. > > > > > + * Currently, we will use maximum address. > > > > > + */ > > > > > + rzg2l_cru_write(cru, AMnMBVALID, AMnMBVALID_MBVALID(cru->num_buf - 1)); > > > > > + > > > > > + for (slot = 0; slot < cru->num_buf; slot++) > > > > > + rzg2l_cru_fill_hw_slot(cru, slot); > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru) > > > > > +{ > > > > > + u32 icnmc; > > > > > + > > > > > + switch (cru->mbus_code) { > > > > > + case MEDIA_BUS_FMT_UYVY8_1X16: > > > > > + icnmc = ICnMC_INF_YUV8_422; > > > > > + cru->input_is_yuv = true; > > > > > + break; > > > > > + default: > > > > > + cru->input_is_yuv = false; > > > > > + icnmc = ICnMC_INF_USER; > > > > > + break; > > > > > + } > > > > > + > > > > > + icnmc |= (rzg2l_cru_read(cru, ICnMC) & ~ICnMC_INF_MASK); > > > > > + > > > > > + /* Set virtual channel CSI2 */ > > > > > + icnmc |= ICnMC_VCSEL(cru->csi.channel); > > > > > + > > > > > + rzg2l_cru_write(cru, ICnMC, icnmc); > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru) > > > > > +{ > > > > > + u32 icndmr; > > > > > + > > > > > + if (cru->is_csi) > > > > > + rzg2l_cru_csi2_setup(cru); > > > > > + > > > > > + /* Output format */ > > > > > + switch (cru->format.pixelformat) { > > > > > + case V4L2_PIX_FMT_UYVY: > > > > > + icndmr = ICnDMR_YCMODE_UYVY; > > > > > + cru->output_is_yuv = true; > > > > > + break; > > > > > + default: > > > > > + dev_err(cru->dev, "Invalid pixelformat (0x%x)\n", > > > > > + cru->format.pixelformat); > > > > > + return -EINVAL; > > > > > + } > > > > > + > > > > > + /* If input and output use same colorspace, do bypass mode */ > > > > > + if (cru->output_is_yuv == cru->input_is_yuv) > > > > > + rzg2l_cru_write(cru, ICnMC, > > > > > + rzg2l_cru_read(cru, ICnMC) | ICnMC_CSCTHR); > > > > > + else > > > > > + rzg2l_cru_write(cru, ICnMC, > > > > > + rzg2l_cru_read(cru, ICnMC) & (~ICnMC_CSCTHR)); > > > > > + > > > > > + /* Set output data format */ > > > > > + rzg2l_cru_write(cru, ICnDMR, icndmr); > > > > > + > > > > > + return 0; > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_set_stream(struct rzg2l_cru_dev *cru, int on) > > > > > +{ > > > > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > > > > + struct media_pipeline *pipe; > > > > > + struct v4l2_subdev *sd; > > > > > + struct media_pad *pad; > > > > > + unsigned long flags; > > > > > + int ret; > > > > > + > > > > > + pad = media_pad_remote_pad_first(&cru->pad); > > > > > + if (!pad) > > > > > + return -EPIPE; > > > > > + > > > > > + sd = media_entity_to_v4l2_subdev(pad->entity); > > > > > + > > > > > + if (!on) { > > > > > + media_pipeline_stop(&cru->vdev.entity); > > > > > + return v4l2_subdev_call(sd, video, s_stream, 0); > > > > > + } > > > > > + > > > > > + ret = rzg2l_cru_mc_validate_format(cru, sd, pad); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + rzg2l_csi2_dphy_setting(csi2, 1); > > > > > + > > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > > + > > > > > + /* Select a video input */ > > > > > + if (cru->is_csi) > > > > > + rzg2l_cru_write(cru, CRUnCTRL, CRUnCTRL_VINSEL(0)); > > > > > + > > > > > + /* Cancel the software reset for image processing block */ > > > > > + rzg2l_cru_write(cru, CRUnRST, CRUnRST_VRESETN); > > > > > + > > > > > + /* Disable and clear the interrupt before using */ > > > > > + rzg2l_cru_write(cru, CRUnIE, 0); > > > > > + rzg2l_cru_write(cru, CRUnINTS, 0x001f000f); > > > > > + > > > > > + /* Initialize the AXI master */ > > > > > + rzg2l_cru_initialize_axi(cru); > > > > > + > > > > > + /* Initialize image convert */ > > > > > + ret = rzg2l_cru_initialize_image_conv(cru); > > > > > + if (ret) { > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > + return ret; > > > > > + } > > > > > + > > > > > + /* Enable interrupt */ > > > > > + rzg2l_cru_write(cru, CRUnIE, CRUnIE_EFE); > > > > > + > > > > > + /* Enable image processing reception */ > > > > > + rzg2l_cru_write(cru, ICnEN, ICnEN_ICEN); > > > > > + > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > + > > > > > + pipe = sd->entity.pipe ? sd->entity.pipe : &cru->vdev.pipe; > > > > > + ret = media_pipeline_start(&cru->vdev.entity, pipe); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + clk_disable_unprepare(cru->vclk); > > > > > + > > > > > + rzg2l_csi2_mipi_link_setting(csi2, 1); > > > > > + > > > > > + ret = clk_prepare_enable(cru->vclk); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + rzg2l_csi2_cmn_rstb_deassert(csi2); > > > > > + > > > > > + ret = v4l2_subdev_call(sd, video, s_stream, 1); > > > > > + if (ret == -ENOIOCTLCMD) > > > > > + ret = 0; > > > > > + if (ret) > > > > > + media_pipeline_stop(&cru->vdev.entity); > > > > > + > > > > > + return ret; > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_stop_streaming(struct rzg2l_cru_dev *cru) > > > > > +{ > > > > > + struct rzg2l_csi2 *csi2 = sd_to_csi2(cru->csi.subdev); > > > > > + u32 amnfifopntr, amnfifopntr_w, amnfifopntr_r_y; > > > > > + unsigned int retries = 0; > > > > > + unsigned long flags; > > > > > + u32 icnms; > > > > > + > > > > > + cru->state = RZG2L_CRU_DMA_STOPPING; > > > > > + > > > > > + rzg2l_cru_set_stream(cru, 0); > > > > > + > > > > > + rzg2l_csi2_dphy_setting(csi2, 0); > > > > > + > > > > > + rzg2l_csi2_mipi_link_setting(csi2, 0); > > > > > + > > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > > + > > > > > + /* Disable and clear the interrupt */ > > > > > + rzg2l_cru_write(cru, CRUnIE, 0); > > > > > + rzg2l_cru_write(cru, CRUnINTS, 0x001F0F0F); > > > > > + > > > > > + /* Stop the operation of image conversion */ > > > > > + rzg2l_cru_write(cru, ICnEN, 0); > > > > > + > > > > > + /* Wait for streaming to stop */ > > > > > + while ((rzg2l_cru_read(cru, ICnMS) & ICnMS_IA) && retries++ < RZG2L_RETRIES) { > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > + msleep(RZG2L_TIMEOUT_MS); > > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > > + } > > > > > + > > > > > + icnms = rzg2l_cru_read(cru, ICnMS) & ICnMS_IA; > > > > > + if (icnms) > > > > > + dev_err(cru->dev, "Failed stop HW, something is seriously broken\n"); > > > > > + > > > > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > > > > + > > > > > + /* Wait until the FIFO becomes empty */ > > > > > + for (retries = 5; retries > 0; retries--) { > > > > > + amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); > > > > > + > > > > > + amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; > > > > > + amnfifopntr_r_y = > > > > > + (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; > > > > > + if (amnfifopntr_w == amnfifopntr_r_y) > > > > > + break; > > > > > + > > > > > + usleep_range(10, 20); > > > > > + } > > > > > + > > > > > + /* Notify that FIFO is not empty here */ > > > > > + if (!retries) > > > > > + dev_err(cru->dev, "Failed to empty FIFO\n"); > > > > > + > > > > > + /* Stop AXI bus */ > > > > > + rzg2l_cru_write(cru, AMnAXISTP, AMnAXISTP_AXI_STOP); > > > > > + > > > > > + /* Wait until the AXI bus stop */ > > > > > + for (retries = 5; retries > 0; retries--) { > > > > > + if (rzg2l_cru_read(cru, AMnAXISTPACK) & > > > > > + AMnAXISTPACK_AXI_STOP_ACK) > > > > > + break; > > > > > + > > > > > + usleep_range(10, 20); > > > > > + }; > > > > > + > > > > > + /* Notify that AXI bus can not stop here */ > > > > > + if (!retries) > > > > > + dev_err(cru->dev, "Failed to stop AXI bus\n"); > > > > > + > > > > > + /* Cancel the AXI bus stop request */ > > > > > + rzg2l_cru_write(cru, AMnAXISTP, 0); > > > > > + > > > > > + /* Resets the image processing module */ > > > > > + rzg2l_cru_write(cru, CRUnRST, 0); > > > > > + > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > + > > > > > + /* Set reset state */ > > > > > + reset_control_assert(cru->aresetn); > > > > > +} > > > > > + > > > > > +static int rzg2l_cru_start_streaming_vq(struct vb2_queue *vq, unsigned int count) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > > > + int ret; > > > > > + > > > > > + /* Release reset state */ > > > > > + ret = reset_control_deassert(cru->aresetn); > > > > > + if (ret) { > > > > > + dev_err(cru->dev, "failed to deassert aresetn\n"); > > > > > + return ret; > > > > > + } > > > > > + > > > > > + /* Allocate scratch buffer. */ > > > > > + cru->scratch = dma_alloc_coherent(cru->dev, cru->format.sizeimage, > > > > > + &cru->scratch_phys, GFP_KERNEL); > > > > > + if (!cru->scratch) { > > > > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > > > > + dev_err(cru->dev, "Failed to allocate scratch buffer\n"); > > > > > + return -ENOMEM; > > > > > + } > > > > > + > > > > > + cru->sequence = 0; > > > > > + > > > > > + ret = rzg2l_cru_set_stream(cru, 1); > > > > > + if (ret) { > > > > > + return_unused_buffers(cru, VB2_BUF_STATE_QUEUED); > > > > > + goto out; > > > > > + } > > > > > + > > > > > + cru->state = RZG2L_CRU_DMA_STARTING; > > > > > + > > > > > + dev_dbg(cru->dev, "Starting to capture\n"); > > > > > + > > > > > +out: > > > > > + if (ret) > > > > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > > > > + cru->scratch_phys); > > > > > + > > > > > + return ret; > > > > > +} > > > > > + > > > > > +static void rzg2l_cru_stop_streaming_vq(struct vb2_queue *vq) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = vb2_get_drv_priv(vq); > > > > > + > > > > > + rzg2l_cru_stop_streaming(cru); > > > > > + > > > > > + /* Free scratch buffer */ > > > > > + dma_free_coherent(cru->dev, cru->format.sizeimage, cru->scratch, > > > > > + cru->scratch_phys); > > > > > + > > > > > + return_unused_buffers(cru, VB2_BUF_STATE_ERROR); > > > > > +} > > > > > + > > > > > +static const struct vb2_ops rzg2l_cru_qops = { > > > > > + .queue_setup = rzg2l_cru_queue_setup, > > > > > + .buf_prepare = rzg2l_cru_buffer_prepare, > > > > > + .buf_queue = rzg2l_cru_buffer_queue, > > > > > + .start_streaming = rzg2l_cru_start_streaming_vq, > > > > > + .stop_streaming = rzg2l_cru_stop_streaming_vq, > > > > > + .wait_prepare = vb2_ops_wait_prepare, > > > > > + .wait_finish = vb2_ops_wait_finish, > > > > > +}; > > > > > + > > > > > +static irqreturn_t rzg2l_cru_irq(int irq, void *data) > > > > > +{ > > > > > + struct rzg2l_cru_dev *cru = data; > > > > > + unsigned int handled = 0; > > > > > + unsigned long flags; > > > > > + u32 irq_status; > > > > > + u32 amnmbs; > > > > > + int slot; > > > > > + > > > > > + spin_lock_irqsave(&cru->qlock, flags); > > > > > + > > > > > + irq_status = rzg2l_cru_read(cru, CRUnINTS); > > > > > + if (!irq_status) > > > > > + goto done; > > > > > + > > > > > + handled = 1; > > > > > + > > > > > + rzg2l_cru_write(cru, CRUnINTS, rzg2l_cru_read(cru, CRUnINTS)); > > > > > + > > > > > + /* Nothing to do if capture status is 'RZG2L_CRU_DMA_STOPPED' */ > > > > > + if (cru->state == RZG2L_CRU_DMA_STOPPED) { > > > > > + dev_dbg(cru->dev, "IRQ while state stopped\n"); > > > > > + goto done; > > > > > + } > > > > > + > > > > > + /* Increase stop retries if capture status is 'RZG2L_CRU_DMA_STOPPING' */ > > > > > + if (cru->state == RZG2L_CRU_DMA_STOPPING) { > > > > > + if (irq_status & CRUnINTS_SFS) > > > > > + dev_dbg(cru->dev, "IRQ while state stopping\n"); > > > > > + goto done; > > > > > + } > > > > > + > > > > > + /* Prepare for capture and update state */ > > > > > + amnmbs = rzg2l_cru_read(cru, AMnMBS); > > > > > + slot = amnmbs & AMnMBS_MBSTS; > > > > > + > > > > > + /* > > > > > + * AMnMBS.MBSTS indicates the destination of Memory Bank (MB). > > > > > + * Recalculate to get the current transfer complete MB. > > > > > + */ > > > > > + if (slot == 0) > > > > > + slot = cru->num_buf - 1; > > > > > + else > > > > > + slot--; > > > > > + > > > > > + /* > > > > > + * To hand buffers back in a known order to userspace start > > > > > + * to capture first from slot 0. > > > > > + */ > > > > > + if (cru->state == RZG2L_CRU_DMA_STARTING) { > > > > > + if (slot != 0) { > > > > > + dev_dbg(cru->dev, "Starting sync slot: %d\n", slot); > > > > > + goto done; > > > > > + } > > > > > + > > > > > + dev_dbg(cru->dev, "Capture start synced!\n"); > > > > > + cru->state = RZG2L_CRU_DMA_RUNNING; > > > > > + } > > > > > + > > > > > + /* Capture frame */ > > > > > + if (cru->queue_buf[slot]) { > > > > > + cru->queue_buf[slot]->field = cru->format.field; > > > > > + cru->queue_buf[slot]->sequence = cru->sequence; > > > > > + cru->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); > > > > > + vb2_buffer_done(&cru->queue_buf[slot]->vb2_buf, > > > > > + VB2_BUF_STATE_DONE); > > > > > + cru->queue_buf[slot] = NULL; > > > > > + } else { > > > > > + /* Scratch buffer was used, dropping frame. */ > > > > > + dev_dbg(cru->dev, "Dropping frame %u\n", cru->sequence); > > > > > + } > > > > > + > > > > > + cru->sequence++; > > > > > + > > > > > + /* Prepare for next frame */ > > > > > + rzg2l_cru_fill_hw_slot(cru, slot); > > > > > + > > > > > +done: > > > > > + spin_unlock_irqrestore(&cru->qlock, flags); > > > > > + > > > > > + return IRQ_RETVAL(handled); > > > > > +} > > > > > + > > > > > +void rzg2l_cru_dma_unregister(struct rzg2l_cru_dev *cru) > > > > > +{ > > > > > + mutex_destroy(&cru->lock); > > > > > + > > > > > + v4l2_device_unregister(&cru->v4l2_dev); > > > > > + reset_control_assert(cru->presetn); > > > > > +} > > > > > + > > > > > +int rzg2l_cru_dma_register(struct rzg2l_cru_dev *cru, int irq) > > > > > +{ > > > > > + struct vb2_queue *q = &cru->queue; > > > > > + unsigned int i; > > > > > + int ret; > > > > > + > > > > > + ret = reset_control_deassert(cru->presetn); > > > > > + if (ret) { > > > > > + dev_err(cru->dev, "failed to deassert presetn\n"); > > > > > + return ret; > > > > > + } > > > > > > > > Shouldn't this be done when starting streaming instead ? > > > > > > Agreed, Ill move it there. > > > > > > > > + > > > > > + /* Initialize the top-level structure */ > > > > > + ret = v4l2_device_register(cru->dev, &cru->v4l2_dev); > > > > > + if (ret) > > > > > + return ret; > > > > > + > > > > > + mutex_init(&cru->lock); > > > > > + INIT_LIST_HEAD(&cru->buf_list); > > > > > + > > > > > + spin_lock_init(&cru->qlock); > > > > > + > > > > > + cru->state = RZG2L_CRU_DMA_STOPPED; > > > > > + > > > > > + for (i = 0; i < HW_BUFFER_MAX; i++) > > > > > + cru->queue_buf[i] = NULL; > > > > > + > > > > > + /* buffer queue */ > > > > > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > > > > > + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; > > > > > > > > No VB2_READ please, that's very inefficient. > > > > > > OK, I'll drop it. > > > > > > > > + q->lock = &cru->lock; > > > > > + q->drv_priv = cru; > > > > > + q->buf_struct_size = sizeof(struct rzg2l_cru_buffer); > > > > > + q->ops = &rzg2l_cru_qops; > > > > > + q->mem_ops = &vb2_dma_contig_memops; > > > > > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > > > > > + q->min_buffers_needed = 4; > > > > > > > > Does the hardware really require 4 buffers to operate ? > > > > > > v4l2-compliance complains about sequnce mismatch when set to 3. > > > > Then maybe there's another problem somewhere ? What's the minimum number > > of buffers the hardware can operate with ? > > FIFO depth is 16. We are using the similar code as rcar-vin where we > use the scratch buffer (and also the rcar-vin sets the > min_buffers_needed to 4) Wow, that's a deep hardware FIFO ! What happens at the hardware level if a buffer completes and no other buffer is present in the FIFO ?
Hi Laurent, On Mon, Sep 26, 2022 at 5:29 PM Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > Hi Prabhakar, > > On Mon, Sep 26, 2022 at 05:24:47PM +0100, Lad, Prabhakar wrote: > > On Mon, Sep 26, 2022 at 9:59 AM Laurent Pinchart wrote: > > > On Fri, Sep 23, 2022 at 08:02:12PM +0100, Lad, Prabhakar wrote: > > > > On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart wrote: > > > > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > > > > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > > > > > > > > > Based on a patch in the BSP by Hien Huynh > > > > > > <hien.huynh.px@renesas.com> > > > > > > > > > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > > > > --- > > > > > > v1 -> v2 > > > > > > * No change > > > > > > > > > > > > RFC v2 -> v1 > > > > > > * Moved the driver to renesas folder > > > > > > * Fixed review comments pointed by Jacopo > > > > > > > > > > > > RFC v1 -> RFC v2 > > > > > > * Dropped group > > > > > > * Dropped CSI subdev and implemented as new driver > > > > > > * Dropped "mc_" from function names > > > > > > * Moved the driver to renesas folder > > > > > > --- > > > > > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > > > > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > > > > > > > > > I'd merge those two files together, they both handle the video node. > > > > > There's a comment below that recommends adding a subdev, that should > > > > > then go to a separate file. > > > > > > > > OK, I'll merge these files into rzg2l-video.c. > > > > > > > > > > 6 files changed, 1669 insertions(+) > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > index 57c40bb499df..08ff0e96b3f5 100644 > > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > > > > > > > > > To compile this driver as a module, choose M here: the > > > > > > module will be called rzg2l-csi2. > > > > > > + > > > > > > +config VIDEO_RZG2L_CRU > > > > > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > > > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > > > > + depends on V4L_PLATFORM_DRIVERS > > > > > > + depends on VIDEO_DEV && OF > > > > > > + select MEDIA_CONTROLLER > > > > > > + select V4L2_FWNODE > > > > > > + select VIDEOBUF2_DMA_CONTIG > > > > > > + select VIDEO_RZG2L_CSI2 > > > > > > > > > > Is this required, can't the CRU be used with a parallel sensor without > > > > > the CSI-2 receiver ? > > > > > > > > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > > > > > > > > > + select VIDEO_V4L2_SUBDEV_API > > > > > > + help > > > > > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > > > > > + Unit (CRU) driver. > > > > > > + > > > > > > + To compile this driver as a module, choose M here: the > > > > > > + module will be called rzg2l-cru. > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > index 91ea97a944e6..7628809e953f 100644 > > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > @@ -1,3 +1,6 @@ > > > > > > # SPDX-License-Identifier: GPL-2.0 > > > > > > > > > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > > > > > + > > > > > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > > > > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > new file mode 100644 > > > > > > index 000000000000..b5d4110b1913 > > > > > > --- /dev/null > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > @@ -0,0 +1,395 @@ > > > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > > > +/* > > > > > > + * Driver for Renesas RZ/G2L CRU > > > > > > + * > > > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > > > + * > > > > > > + * Based on Renesas R-Car VIN > > > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > > > + * Copyright (C) 2008 Magnus Damm > > > > > > + */ > > > > > > + > > > > > > +#include <linux/clk.h> > > > > > > +#include <linux/module.h> > > > > > > +#include <linux/mod_devicetable.h> > > > > > > +#include <linux/of.h> > > > > > > +#include <linux/of_device.h> > > > > > > +#include <linux/of_graph.h> > > > > > > +#include <linux/platform_device.h> > > > > > > +#include <linux/pm_runtime.h> > > > > > > + > > > > > > +#include <media/v4l2-fwnode.h> > > > > > > +#include <media/v4l2-mc.h> > > > > > > + > > > > > > +#include "rzg2l-cru.h" > > > > > > + > > > > > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > > > > > > > > > As this macro is only used to get the rzg2l_cru_dev pointer from the > > > > > v4l2_async_notifier pointer, you can replace it with > > > > > > > > > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > > > > > > > > > I would also turn it into a static inline function for additional > > > > > compile-time type safety. > > > > > > > > OK, I will do it as mentioned above. > > > > > > > > > > + > > > > > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > > > > > + unsigned int notification) > > > > > > +{ > > > > > > + struct media_entity *entity; > > > > > > + struct rzg2l_cru_dev *cru; > > > > > > + struct media_pad *csi_pad; > > > > > > + struct v4l2_subdev *sd; > > > > > > + int ret; > > > > > > + > > > > > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > > > > > + if (ret) > > > > > > + return ret; > > > > > > + > > > > > > + /* Only care about link enablement for CRU nodes. */ > > > > > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > > > > > + return 0; > > > > > > + > > > > > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > > > > > + /* > > > > > > + * Don't allow link changes if any entity in the graph is > > > > > > + * streaming, modifying the CHSEL register fields can disrupt > > > > > > + * running streams. > > > > > > + */ > > > > > > + media_device_for_each_entity(entity, &cru->mdev) > > > > > > + if (media_entity_is_streaming(entity)) > > > > > > + return -EBUSY; > > > > > > + > > > > > > + mutex_lock(&cru->mdev_lock); > > > > > > + > > > > > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > > > > > + if (csi_pad) { > > > > > > + ret = -EMLINK; > > > > > > + goto out; > > > > > > + } > > > > > > + > > > > > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > > > > > + if (cru->csi.subdev == sd) { > > > > > > + cru->csi.channel = link->source->index - 1; > > > > > > + cru->is_csi = true; > > > > > > + } else { > > > > > > + ret = -ENODEV; > > > > > > + } > > > > > > + > > > > > > +out: > > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > > + > > > > > > + return ret; > > > > > > +} > > > > > > + > > > > > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > > > > > + .link_notify = rzg2l_cru_csi2_link_notify, > > > > > > +}; > > > > > > + > > > > > > +/* ----------------------------------------------------------------------------- > > > > > > + * Group async notifier > > > > > > + */ > > > > > > + > > > > > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > > > > > +{ > > > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > > > + unsigned int i; > > > > > > + int ret; > > > > > > + > > > > > > + ret = media_device_register(&cru->mdev); > > > > > > + if (ret) > > > > > > + return ret; > > > > > > > > > > I'd move the v4l2_device_register() call here, as it's the V4L2 > > > > > counterpart of the media device, and handling them together would be > > > > > best. > > > > > > > > OK. > > > > > > > > > > + > > > > > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > > > > > + if (ret) { > > > > > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > > > > > + return ret; > > > > > > + } > > > > > > + > > > > > > + if (!video_is_registered(&cru->vdev)) { > > > > > > > > > > Can this happen ? > > > > > > > > No, I'll drop this check. > > > > > > > > > > + ret = rzg2l_cru_v4l2_register(cru); > > > > > > + if (ret) > > > > > > + return ret; > > > > > > + } > > > > > > + > > > > > > + /* Create all media device links between CRU and CSI-2's. */ > > > > > > + /* > > > > > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > > > > > + * should be implemented by streams API which is under development > > > > > > + * so for now just link it to VC0 > > > > > > + */ > > > > > > > > > > The streams API won't require more links, so I'd drop the comment and > > > > > the loop and create a single link. > > > > > > > > OK. > > > > > > > > > > + for (i = 1; i <= 1; i++) { > > > > > > + struct media_entity *source, *sink; > > > > > > + > > > > > > + source = &cru->csi.subdev->entity; > > > > > > + sink = &cru->vdev.entity; > > > > > > > > > > Hmmm... I'd recommend adding a subdev to model the image processing > > > > > pipeline of the CRU, between the CSI-2 receiver and the video node. That > > > > > will help when you'll add support for parallel sensors, and it will also > > > > > be needed by the streams API to select which virtual channel to capture. > > > >/ > > > > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? > > > > > > I think MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER would be more > > > appropriate. > > > > OK I will use MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER. As this will be > > just like a switch should I be implementing the get_fmt/set_fmt > > callbacks? > > Yes, subdev operations need to be implemented, especially given that the > CRU implements color space conversion, so the input and output formats > of the subdev can be different. > OK, the reason I asked as, for the sink pad the format of IP subdev will be the same as remote source pad (i.e. either from CSI/parallel subdev) and for the source pad this will be the same as format on the video node (ie CRU output). get_fmt -> we get the subdev pad fmt of the remote source and return it. set_fmt -> we just pass through, as the fmt is set on the video dev node. Does the above sound good? Cheers, Prabhakar
Hi Prabhakar, On Mon, Sep 26, 2022 at 06:27:40PM +0100, Lad, Prabhakar wrote: > On Mon, Sep 26, 2022 at 5:29 PM Laurent Pinchart wrote: > > On Mon, Sep 26, 2022 at 05:24:47PM +0100, Lad, Prabhakar wrote: > > > On Mon, Sep 26, 2022 at 9:59 AM Laurent Pinchart wrote: > > > > On Fri, Sep 23, 2022 at 08:02:12PM +0100, Lad, Prabhakar wrote: > > > > > On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart wrote: > > > > > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > > > > > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > > > > > > > > > > > Based on a patch in the BSP by Hien Huynh > > > > > > > <hien.huynh.px@renesas.com> > > > > > > > > > > > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > > > > > --- > > > > > > > v1 -> v2 > > > > > > > * No change > > > > > > > > > > > > > > RFC v2 -> v1 > > > > > > > * Moved the driver to renesas folder > > > > > > > * Fixed review comments pointed by Jacopo > > > > > > > > > > > > > > RFC v1 -> RFC v2 > > > > > > > * Dropped group > > > > > > > * Dropped CSI subdev and implemented as new driver > > > > > > > * Dropped "mc_" from function names > > > > > > > * Moved the driver to renesas folder > > > > > > > --- > > > > > > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > > > > > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > > > > > > > > > > > I'd merge those two files together, they both handle the video node. > > > > > > There's a comment below that recommends adding a subdev, that should > > > > > > then go to a separate file. > > > > > > > > > > OK, I'll merge these files into rzg2l-video.c. > > > > > > > > > > > > 6 files changed, 1669 insertions(+) > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > > > > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > > index 57c40bb499df..08ff0e96b3f5 100644 > > > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > > > > > > > > > > > To compile this driver as a module, choose M here: the > > > > > > > module will be called rzg2l-csi2. > > > > > > > + > > > > > > > +config VIDEO_RZG2L_CRU > > > > > > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > > > > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > > > > > + depends on V4L_PLATFORM_DRIVERS > > > > > > > + depends on VIDEO_DEV && OF > > > > > > > + select MEDIA_CONTROLLER > > > > > > > + select V4L2_FWNODE > > > > > > > + select VIDEOBUF2_DMA_CONTIG > > > > > > > + select VIDEO_RZG2L_CSI2 > > > > > > > > > > > > Is this required, can't the CRU be used with a parallel sensor without > > > > > > the CSI-2 receiver ? > > > > > > > > > > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > > > > > > > > > > > + select VIDEO_V4L2_SUBDEV_API > > > > > > > + help > > > > > > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > > > > > > + Unit (CRU) driver. > > > > > > > + > > > > > > > + To compile this driver as a module, choose M here: the > > > > > > > + module will be called rzg2l-cru. > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > > index 91ea97a944e6..7628809e953f 100644 > > > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > > @@ -1,3 +1,6 @@ > > > > > > > # SPDX-License-Identifier: GPL-2.0 > > > > > > > > > > > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > > > > > > + > > > > > > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > > > > > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > > new file mode 100644 > > > > > > > index 000000000000..b5d4110b1913 > > > > > > > --- /dev/null > > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > > @@ -0,0 +1,395 @@ > > > > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > > > > +/* > > > > > > > + * Driver for Renesas RZ/G2L CRU > > > > > > > + * > > > > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > > > > + * > > > > > > > + * Based on Renesas R-Car VIN > > > > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > > > > + * Copyright (C) 2008 Magnus Damm > > > > > > > + */ > > > > > > > + > > > > > > > +#include <linux/clk.h> > > > > > > > +#include <linux/module.h> > > > > > > > +#include <linux/mod_devicetable.h> > > > > > > > +#include <linux/of.h> > > > > > > > +#include <linux/of_device.h> > > > > > > > +#include <linux/of_graph.h> > > > > > > > +#include <linux/platform_device.h> > > > > > > > +#include <linux/pm_runtime.h> > > > > > > > + > > > > > > > +#include <media/v4l2-fwnode.h> > > > > > > > +#include <media/v4l2-mc.h> > > > > > > > + > > > > > > > +#include "rzg2l-cru.h" > > > > > > > + > > > > > > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > > > > > > > > > > > As this macro is only used to get the rzg2l_cru_dev pointer from the > > > > > > v4l2_async_notifier pointer, you can replace it with > > > > > > > > > > > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > > > > > > > > > > > I would also turn it into a static inline function for additional > > > > > > compile-time type safety. > > > > > > > > > > OK, I will do it as mentioned above. > > > > > > > > > > > > + > > > > > > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > > > > > > + unsigned int notification) > > > > > > > +{ > > > > > > > + struct media_entity *entity; > > > > > > > + struct rzg2l_cru_dev *cru; > > > > > > > + struct media_pad *csi_pad; > > > > > > > + struct v4l2_subdev *sd; > > > > > > > + int ret; > > > > > > > + > > > > > > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > > > > > > + if (ret) > > > > > > > + return ret; > > > > > > > + > > > > > > > + /* Only care about link enablement for CRU nodes. */ > > > > > > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > > > > > > + return 0; > > > > > > > + > > > > > > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > > > > > > + /* > > > > > > > + * Don't allow link changes if any entity in the graph is > > > > > > > + * streaming, modifying the CHSEL register fields can disrupt > > > > > > > + * running streams. > > > > > > > + */ > > > > > > > + media_device_for_each_entity(entity, &cru->mdev) > > > > > > > + if (media_entity_is_streaming(entity)) > > > > > > > + return -EBUSY; > > > > > > > + > > > > > > > + mutex_lock(&cru->mdev_lock); > > > > > > > + > > > > > > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > > > > > > + if (csi_pad) { > > > > > > > + ret = -EMLINK; > > > > > > > + goto out; > > > > > > > + } > > > > > > > + > > > > > > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > > > > > > + if (cru->csi.subdev == sd) { > > > > > > > + cru->csi.channel = link->source->index - 1; > > > > > > > + cru->is_csi = true; > > > > > > > + } else { > > > > > > > + ret = -ENODEV; > > > > > > > + } > > > > > > > + > > > > > > > +out: > > > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > > > + > > > > > > > + return ret; > > > > > > > +} > > > > > > > + > > > > > > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > > > > > > + .link_notify = rzg2l_cru_csi2_link_notify, > > > > > > > +}; > > > > > > > + > > > > > > > +/* ----------------------------------------------------------------------------- > > > > > > > + * Group async notifier > > > > > > > + */ > > > > > > > + > > > > > > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > > > > > > +{ > > > > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > > > > + unsigned int i; > > > > > > > + int ret; > > > > > > > + > > > > > > > + ret = media_device_register(&cru->mdev); > > > > > > > + if (ret) > > > > > > > + return ret; > > > > > > > > > > > > I'd move the v4l2_device_register() call here, as it's the V4L2 > > > > > > counterpart of the media device, and handling them together would be > > > > > > best. > > > > > > > > > > OK. > > > > > > > > > > > > + > > > > > > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > > > > > > + if (ret) { > > > > > > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > > > > > > + return ret; > > > > > > > + } > > > > > > > + > > > > > > > + if (!video_is_registered(&cru->vdev)) { > > > > > > > > > > > > Can this happen ? > > > > > > > > > > No, I'll drop this check. > > > > > > > > > > > > + ret = rzg2l_cru_v4l2_register(cru); > > > > > > > + if (ret) > > > > > > > + return ret; > > > > > > > + } > > > > > > > + > > > > > > > + /* Create all media device links between CRU and CSI-2's. */ > > > > > > > + /* > > > > > > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > > > > > > + * should be implemented by streams API which is under development > > > > > > > + * so for now just link it to VC0 > > > > > > > + */ > > > > > > > > > > > > The streams API won't require more links, so I'd drop the comment and > > > > > > the loop and create a single link. > > > > > > > > > > OK. > > > > > > > > > > > > + for (i = 1; i <= 1; i++) { > > > > > > > + struct media_entity *source, *sink; > > > > > > > + > > > > > > > + source = &cru->csi.subdev->entity; > > > > > > > + sink = &cru->vdev.entity; > > > > > > > > > > > > Hmmm... I'd recommend adding a subdev to model the image processing > > > > > > pipeline of the CRU, between the CSI-2 receiver and the video node. That > > > > > > will help when you'll add support for parallel sensors, and it will also > > > > > > be needed by the streams API to select which virtual channel to capture. > > > > >/ > > > > > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? > > > > > > > > I think MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER would be more > > > > appropriate. > > > > > > OK I will use MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER. As this will be > > > just like a switch should I be implementing the get_fmt/set_fmt > > > callbacks? > > > > Yes, subdev operations need to be implemented, especially given that the > > CRU implements color space conversion, so the input and output formats > > of the subdev can be different. > > OK, the reason I asked as, for the sink pad the format of IP subdev > will be the same as remote source pad (i.e. either from CSI/parallel > subdev) and for the source pad this will be the same as format on the > video node (ie CRU output). > > get_fmt -> we get the subdev pad fmt of the remote source and return it. > set_fmt -> we just pass through, as the fmt is set on the video dev node. > > Does the above sound good? No, subdevs must not query each other to implement those operations. Subdevs are configured by userspace separately from each other, so a subdev set_fmt handler must accept any format that is valid for the subdev, regardless of how the connected subdevs or video nodes are configured. get_fmt just returns the format that has been set on the subdev. Validation of the pipeline, to check that connected pads have a compatible format, is done when starting the stream.
Hi Laurent, On Mon, Sep 26, 2022 at 7:11 PM Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > Hi Prabhakar, > > On Mon, Sep 26, 2022 at 06:27:40PM +0100, Lad, Prabhakar wrote: > > On Mon, Sep 26, 2022 at 5:29 PM Laurent Pinchart wrote: > > > On Mon, Sep 26, 2022 at 05:24:47PM +0100, Lad, Prabhakar wrote: > > > > On Mon, Sep 26, 2022 at 9:59 AM Laurent Pinchart wrote: > > > > > On Fri, Sep 23, 2022 at 08:02:12PM +0100, Lad, Prabhakar wrote: > > > > > > On Thu, Sep 22, 2022 at 4:29 PM Laurent Pinchart wrote: > > > > > > > On Tue, Sep 06, 2022 at 12:04:06AM +0100, Lad Prabhakar wrote: > > > > > > > > Add v4l driver for Renesas RZ/G2L Camera data Receiving Unit. > > > > > > > > > > > > > > > > Based on a patch in the BSP by Hien Huynh > > > > > > > > <hien.huynh.px@renesas.com> > > > > > > > > > > > > > > > > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > > > > > > --- > > > > > > > > v1 -> v2 > > > > > > > > * No change > > > > > > > > > > > > > > > > RFC v2 -> v1 > > > > > > > > * Moved the driver to renesas folder > > > > > > > > * Fixed review comments pointed by Jacopo > > > > > > > > > > > > > > > > RFC v1 -> RFC v2 > > > > > > > > * Dropped group > > > > > > > > * Dropped CSI subdev and implemented as new driver > > > > > > > > * Dropped "mc_" from function names > > > > > > > > * Moved the driver to renesas folder > > > > > > > > --- > > > > > > > > .../media/platform/renesas/rzg2l-cru/Kconfig | 17 + > > > > > > > > .../media/platform/renesas/rzg2l-cru/Makefile | 3 + > > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-core.c | 395 ++++++++++ > > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 152 ++++ > > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-dma.c | 734 ++++++++++++++++++ > > > > > > > > .../platform/renesas/rzg2l-cru/rzg2l-v4l2.c | 368 +++++++++ > > > > > > > > > > > > > > I'd merge those two files together, they both handle the video node. > > > > > > > There's a comment below that recommends adding a subdev, that should > > > > > > > then go to a separate file. > > > > > > > > > > > > OK, I'll merge these files into rzg2l-video.c. > > > > > > > > > > > > > > 6 files changed, 1669 insertions(+) > > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h > > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-dma.c > > > > > > > > create mode 100644 drivers/media/platform/renesas/rzg2l-cru/rzg2l-v4l2.c > > > > > > > > > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Kconfig b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > > > index 57c40bb499df..08ff0e96b3f5 100644 > > > > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Kconfig > > > > > > > > @@ -15,3 +15,20 @@ config VIDEO_RZG2L_CSI2 > > > > > > > > > > > > > > > > To compile this driver as a module, choose M here: the > > > > > > > > module will be called rzg2l-csi2. > > > > > > > > + > > > > > > > > +config VIDEO_RZG2L_CRU > > > > > > > > + tristate "RZ/G2L Camera Receiving Unit (CRU) Driver" > > > > > > > > + depends on ARCH_RENESAS || COMPILE_TEST > > > > > > > > + depends on V4L_PLATFORM_DRIVERS > > > > > > > > + depends on VIDEO_DEV && OF > > > > > > > > + select MEDIA_CONTROLLER > > > > > > > > + select V4L2_FWNODE > > > > > > > > + select VIDEOBUF2_DMA_CONTIG > > > > > > > > + select VIDEO_RZG2L_CSI2 > > > > > > > > > > > > > > Is this required, can't the CRU be used with a parallel sensor without > > > > > > > the CSI-2 receiver ? > > > > > > > > > > > > Yes the CRU can be used with parallel sensors, I'll drop the above select. > > > > > > > > > > > > > > + select VIDEO_V4L2_SUBDEV_API > > > > > > > > + help > > > > > > > > + Support for Renesas RZ/G2L (and alike SoC's) Camera Receiving > > > > > > > > + Unit (CRU) driver. > > > > > > > > + > > > > > > > > + To compile this driver as a module, choose M here: the > > > > > > > > + module will be called rzg2l-cru. > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/Makefile b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > > > index 91ea97a944e6..7628809e953f 100644 > > > > > > > > --- a/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/Makefile > > > > > > > > @@ -1,3 +1,6 @@ > > > > > > > > # SPDX-License-Identifier: GPL-2.0 > > > > > > > > > > > > > > > > obj-$(CONFIG_VIDEO_RZG2L_CSI2) += rzg2l-csi2.o > > > > > > > > + > > > > > > > > +rzg2l-cru-objs = rzg2l-core.o rzg2l-dma.o rzg2l-v4l2.o > > > > > > > > +obj-$(CONFIG_VIDEO_RZG2L_CRU) += rzg2l-cru.o > > > > > > > > diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > > > new file mode 100644 > > > > > > > > index 000000000000..b5d4110b1913 > > > > > > > > --- /dev/null > > > > > > > > +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c > > > > > > > > @@ -0,0 +1,395 @@ > > > > > > > > +// SPDX-License-Identifier: GPL-2.0+ > > > > > > > > +/* > > > > > > > > + * Driver for Renesas RZ/G2L CRU > > > > > > > > + * > > > > > > > > + * Copyright (C) 2022 Renesas Electronics Corp. > > > > > > > > + * > > > > > > > > + * Based on Renesas R-Car VIN > > > > > > > > + * Copyright (C) 2011-2013 Renesas Solutions Corp. > > > > > > > > + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> > > > > > > > > + * Copyright (C) 2008 Magnus Damm > > > > > > > > + */ > > > > > > > > + > > > > > > > > +#include <linux/clk.h> > > > > > > > > +#include <linux/module.h> > > > > > > > > +#include <linux/mod_devicetable.h> > > > > > > > > +#include <linux/of.h> > > > > > > > > +#include <linux/of_device.h> > > > > > > > > +#include <linux/of_graph.h> > > > > > > > > +#include <linux/platform_device.h> > > > > > > > > +#include <linux/pm_runtime.h> > > > > > > > > + > > > > > > > > +#include <media/v4l2-fwnode.h> > > > > > > > > +#include <media/v4l2-mc.h> > > > > > > > > + > > > > > > > > +#include "rzg2l-cru.h" > > > > > > > > + > > > > > > > > +#define v4l2_dev_to_cru(d) container_of(d, struct rzg2l_cru_dev, v4l2_dev) > > > > > > > > > > > > > > As this macro is only used to get the rzg2l_cru_dev pointer from the > > > > > > > v4l2_async_notifier pointer, you can replace it with > > > > > > > > > > > > > > #define notifier_to_cru(n) container_of(n, struct rzg2l_cru_dev, notifier) > > > > > > > > > > > > > > I would also turn it into a static inline function for additional > > > > > > > compile-time type safety. > > > > > > > > > > > > OK, I will do it as mentioned above. > > > > > > > > > > > > > > + > > > > > > > > +static int rzg2l_cru_csi2_link_notify(struct media_link *link, u32 flags, > > > > > > > > + unsigned int notification) > > > > > > > > +{ > > > > > > > > + struct media_entity *entity; > > > > > > > > + struct rzg2l_cru_dev *cru; > > > > > > > > + struct media_pad *csi_pad; > > > > > > > > + struct v4l2_subdev *sd; > > > > > > > > + int ret; > > > > > > > > + > > > > > > > > + ret = v4l2_pipeline_link_notify(link, flags, notification); > > > > > > > > + if (ret) > > > > > > > > + return ret; > > > > > > > > + > > > > > > > > + /* Only care about link enablement for CRU nodes. */ > > > > > > > > + if (!(flags & MEDIA_LNK_FL_ENABLED)) > > > > > > > > + return 0; > > > > > > > > + > > > > > > > > + cru = container_of(link->graph_obj.mdev, struct rzg2l_cru_dev, mdev); > > > > > > > > + /* > > > > > > > > + * Don't allow link changes if any entity in the graph is > > > > > > > > + * streaming, modifying the CHSEL register fields can disrupt > > > > > > > > + * running streams. > > > > > > > > + */ > > > > > > > > + media_device_for_each_entity(entity, &cru->mdev) > > > > > > > > + if (media_entity_is_streaming(entity)) > > > > > > > > + return -EBUSY; > > > > > > > > + > > > > > > > > + mutex_lock(&cru->mdev_lock); > > > > > > > > + > > > > > > > > + csi_pad = media_pad_remote_pad_first(&cru->vdev.entity.pads[0]); > > > > > > > > + if (csi_pad) { > > > > > > > > + ret = -EMLINK; > > > > > > > > + goto out; > > > > > > > > + } > > > > > > > > + > > > > > > > > + sd = media_entity_to_v4l2_subdev(link->source->entity); > > > > > > > > + if (cru->csi.subdev == sd) { > > > > > > > > + cru->csi.channel = link->source->index - 1; > > > > > > > > + cru->is_csi = true; > > > > > > > > + } else { > > > > > > > > + ret = -ENODEV; > > > > > > > > + } > > > > > > > > + > > > > > > > > +out: > > > > > > > > + mutex_unlock(&cru->mdev_lock); > > > > > > > > + > > > > > > > > + return ret; > > > > > > > > +} > > > > > > > > + > > > > > > > > +static const struct media_device_ops rzg2l_cru_media_ops = { > > > > > > > > + .link_notify = rzg2l_cru_csi2_link_notify, > > > > > > > > +}; > > > > > > > > + > > > > > > > > +/* ----------------------------------------------------------------------------- > > > > > > > > + * Group async notifier > > > > > > > > + */ > > > > > > > > + > > > > > > > > +static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) > > > > > > > > +{ > > > > > > > > + struct rzg2l_cru_dev *cru = v4l2_dev_to_cru(notifier->v4l2_dev); > > > > > > > > + unsigned int i; > > > > > > > > + int ret; > > > > > > > > + > > > > > > > > + ret = media_device_register(&cru->mdev); > > > > > > > > + if (ret) > > > > > > > > + return ret; > > > > > > > > > > > > > > I'd move the v4l2_device_register() call here, as it's the V4L2 > > > > > > > counterpart of the media device, and handling them together would be > > > > > > > best. > > > > > > > > > > > > OK. > > > > > > > > > > > > > > + > > > > > > > > + ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); > > > > > > > > + if (ret) { > > > > > > > > + dev_err(cru->dev, "Failed to register subdev nodes\n"); > > > > > > > > + return ret; > > > > > > > > + } > > > > > > > > + > > > > > > > > + if (!video_is_registered(&cru->vdev)) { > > > > > > > > > > > > > > Can this happen ? > > > > > > > > > > > > No, I'll drop this check. > > > > > > > > > > > > > > + ret = rzg2l_cru_v4l2_register(cru); > > > > > > > > + if (ret) > > > > > > > > + return ret; > > > > > > > > + } > > > > > > > > + > > > > > > > > + /* Create all media device links between CRU and CSI-2's. */ > > > > > > > > + /* > > > > > > > > + * TODO: RZ/G2L supports 4 VC0, as support for virtual channels > > > > > > > > + * should be implemented by streams API which is under development > > > > > > > > + * so for now just link it to VC0 > > > > > > > > + */ > > > > > > > > > > > > > > The streams API won't require more links, so I'd drop the comment and > > > > > > > the loop and create a single link. > > > > > > > > > > > > OK. > > > > > > > > > > > > > > + for (i = 1; i <= 1; i++) { > > > > > > > > + struct media_entity *source, *sink; > > > > > > > > + > > > > > > > > + source = &cru->csi.subdev->entity; > > > > > > > > + sink = &cru->vdev.entity; > > > > > > > > > > > > > > Hmmm... I'd recommend adding a subdev to model the image processing > > > > > > > pipeline of the CRU, between the CSI-2 receiver and the video node. That > > > > > > > will help when you'll add support for parallel sensors, and it will also > > > > > > > be needed by the streams API to select which virtual channel to capture. > > > > > >/ > > > > > > just model as a dummy subdev for now (MEDIA_ENT_F_VID_MUX)? > > > > > > > > > > I think MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER would be more > > > > > appropriate. > > > > > > > > OK I will use MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER. As this will be > > > > just like a switch should I be implementing the get_fmt/set_fmt > > > > callbacks? > > > > > > Yes, subdev operations need to be implemented, especially given that the > > > CRU implements color space conversion, so the input and output formats > > > of the subdev can be different. > > > > OK, the reason I asked as, for the sink pad the format of IP subdev > > will be the same as remote source pad (i.e. either from CSI/parallel > > subdev) and for the source pad this will be the same as format on the > > video node (ie CRU output). > > > > get_fmt -> we get the subdev pad fmt of the remote source and return it. > > set_fmt -> we just pass through, as the fmt is set on the video dev node. > > > > Does the above sound good? > > No, subdevs must not query each other to implement those operations. > Subdevs are configured by userspace separately from each other, so a > subdev set_fmt handler must accept any format that is valid for the > subdev, regardless of how the connected subdevs or video nodes are > configured. get_fmt just returns the format that has been set on the > subdev. Validation of the pipeline, to check that connected pads have a > compatible format, is done when starting the stream. > Thanks for reminding me of this ;) . It's the essence of the MC framework. Cheers, Prabhakar