Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/media/platform/nxp/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 28 kB image not shown  

Quelle  imx8mq-mipi-csi2.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * NXP i.MX8MQ SoC series MIPI-CSI2 receiver driver
 *
 * Copyright (C) 2021 Purism SPC
 */


#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/interconnect.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/spinlock.h>

#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#include <media/v4l2-subdev.h>

#define MIPI_CSI2_DRIVER_NAME   "imx8mq-mipi-csi2"
#define MIPI_CSI2_SUBDEV_NAME   MIPI_CSI2_DRIVER_NAME

#define MIPI_CSI2_PAD_SINK   0
#define MIPI_CSI2_PAD_SOURCE   1
#define MIPI_CSI2_PADS_NUM   2

#define MIPI_CSI2_DEF_PIX_WIDTH   640
#define MIPI_CSI2_DEF_PIX_HEIGHT  480

/* Register map definition */

/* i.MX8MQ CSI-2 controller CSR */
#define CSI2RX_CFG_NUM_LANES   0x100
#define CSI2RX_CFG_DISABLE_DATA_LANES  0x104
#define CSI2RX_BIT_ERR    0x108
#define CSI2RX_IRQ_STATUS   0x10c
#define CSI2RX_IRQ_MASK    0x110
#define CSI2RX_IRQ_MASK_ALL   0x1ff
#define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE 0x8
#define CSI2RX_ULPS_STATUS   0x114
#define CSI2RX_PPI_ERRSOT_HS   0x118
#define CSI2RX_PPI_ERRSOTSYNC_HS  0x11c
#define CSI2RX_PPI_ERRESC   0x120
#define CSI2RX_PPI_ERRSYNCESC   0x124
#define CSI2RX_PPI_ERRCONTROL   0x128
#define CSI2RX_CFG_DISABLE_PAYLOAD_0  0x12c
#define CSI2RX_CFG_VID_VC_IGNORE  0x180
#define CSI2RX_CFG_VID_VC   0x184
#define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL 0x188
#define CSI2RX_CFG_DISABLE_PAYLOAD_1  0x130

struct csi_state;

enum {
 ST_POWERED = 1,
 ST_STREAMING = 2,
 ST_SUSPENDED = 4,
};

enum imx8mq_mipi_csi_clk {
 CSI2_CLK_CORE,
 CSI2_CLK_ESC,
 CSI2_CLK_UI,
 CSI2_NUM_CLKS,
};

static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = {
 [CSI2_CLK_CORE] = "core",
 [CSI2_CLK_ESC] = "esc",
 [CSI2_CLK_UI] = "ui",
};

#define CSI2_NUM_CLKS ARRAY_SIZE(imx8mq_mipi_csi_clk_id)

struct imx8mq_plat_data {
 int (*enable)(struct csi_state *state, u32 hs_settle);
 void (*disable)(struct csi_state *state);
 bool use_reg_csr;
};

/*
 * The send level configures the number of entries that must accumulate in
 * the Pixel FIFO before the data will be transferred to the video output.
 * The exact value needed for this configuration is dependent on the rate at
 * which the sensor transfers data to the CSI-2 Controller and the user
 * video clock.
 *
 * The calculation is the classical rate-in rate-out type of problem: If the
 * video bandwidth is 10% faster than the incoming mipi data and the video
 * line length is 500 pixels, then the fifo should be allowed to fill
 * 10% of the line length or 50 pixels. If the gap data is ok, then the level
 * can be set to 16 and ignored.
 */

#define CSI2RX_SEND_LEVEL   64

struct csi_state {
 struct device *dev;
 const struct imx8mq_plat_data *pdata;
 void __iomem *regs;
 struct clk_bulk_data clks[CSI2_NUM_CLKS];
 struct reset_control *rst;
 struct regulator *mipi_phy_regulator;

 struct v4l2_subdev sd;
 struct media_pad pads[MIPI_CSI2_PADS_NUM];
 struct v4l2_async_notifier notifier;
 struct v4l2_subdev *src_sd;

 struct v4l2_mbus_config_mipi_csi2 bus;

 struct mutex lock; /* Protect state */
 u32 state;

 struct regmap *phy_gpr;
 u8 phy_gpr_reg;

 struct icc_path   *icc_path;
 s32    icc_path_bw;
};

/* -----------------------------------------------------------------------------
 * Format helpers
 */


struct csi2_pix_format {
 u32 code;
 u8 width;
};

/* -----------------------------------------------------------------------------
 * i.MX8MQ GPR
 */


#define GPR_CSI2_1_RX_ENABLE  BIT(13)
#define GPR_CSI2_1_VID_INTFC_ENB BIT(12)
#define GPR_CSI2_1_HSEL   BIT(10)
#define GPR_CSI2_1_CONT_CLK_MODE BIT(8)
#define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2)

static int imx8mq_gpr_enable(struct csi_state *state, u32 hs_settle)
{
 regmap_update_bits(state->phy_gpr,
      state->phy_gpr_reg,
      0x3fff,
      GPR_CSI2_1_RX_ENABLE |
      GPR_CSI2_1_VID_INTFC_ENB |
      GPR_CSI2_1_HSEL |
      GPR_CSI2_1_CONT_CLK_MODE |
      GPR_CSI2_1_S_PRG_RXHS_SETTLE(hs_settle));

 return 0;
}

static const struct imx8mq_plat_data imx8mq_data = {
 .enable = imx8mq_gpr_enable,
};

/* -----------------------------------------------------------------------------
 * i.MX8QXP
 */


#define CSI2SS_PL_CLK_INTERVAL_US  100
#define CSI2SS_PL_CLK_TIMEOUT_US  100000

#define CSI2SS_PLM_CTRL    0x0
#define CSI2SS_PLM_CTRL_ENABLE_PL  BIT(0)
#define CSI2SS_PLM_CTRL_VSYNC_OVERRIDE  BIT(9)
#define CSI2SS_PLM_CTRL_HSYNC_OVERRIDE  BIT(10)
#define CSI2SS_PLM_CTRL_VALID_OVERRIDE  BIT(11)
#define CSI2SS_PLM_CTRL_POLARITY_HIGH  BIT(12)
#define CSI2SS_PLM_CTRL_PL_CLK_RUN  BIT(31)

#define CSI2SS_PHY_CTRL    0x4
#define CSI2SS_PHY_CTRL_RX_ENABLE  BIT(0)
#define CSI2SS_PHY_CTRL_AUTO_PD_EN  BIT(1)
#define CSI2SS_PHY_CTRL_DDRCLK_EN  BIT(2)
#define CSI2SS_PHY_CTRL_CONT_CLK_MODE  BIT(3)
#define CSI2SS_PHY_CTRL_RX_HS_SETTLE_MASK GENMASK(9, 4)
#define CSI2SS_PHY_CTRL_RTERM_SEL  BIT(21)
#define CSI2SS_PHY_CTRL_PD   BIT(22)

#define CSI2SS_DATA_TYPE_DISABLE_BF  0x38
#define CSI2SS_DATA_TYPE_DISABLE_BF_MASK GENMASK(23, 0)

#define CSI2SS_CTRL_CLK_RESET   0x44
#define CSI2SS_CTRL_CLK_RESET_EN  BIT(0)

static int imx8qxp_gpr_enable(struct csi_state *state, u32 hs_settle)
{
 int ret;
 u32 val;

 /* Clear format */
 regmap_clear_bits(state->phy_gpr, CSI2SS_DATA_TYPE_DISABLE_BF,
     CSI2SS_DATA_TYPE_DISABLE_BF_MASK);

 regmap_write(state->phy_gpr, CSI2SS_PLM_CTRL, 0x0);

 regmap_write(state->phy_gpr, CSI2SS_PHY_CTRL,
       FIELD_PREP(CSI2SS_PHY_CTRL_RX_HS_SETTLE_MASK, hs_settle) |
       CSI2SS_PHY_CTRL_RX_ENABLE | CSI2SS_PHY_CTRL_DDRCLK_EN |
       CSI2SS_PHY_CTRL_CONT_CLK_MODE | CSI2SS_PHY_CTRL_PD |
       CSI2SS_PHY_CTRL_RTERM_SEL | CSI2SS_PHY_CTRL_AUTO_PD_EN);

 ret = regmap_read_poll_timeout(state->phy_gpr, CSI2SS_PLM_CTRL,
           val, !(val & CSI2SS_PLM_CTRL_PL_CLK_RUN),
           CSI2SS_PL_CLK_INTERVAL_US,
           CSI2SS_PL_CLK_TIMEOUT_US);

 if (ret) {
  dev_err(state->dev, "Timeout waiting for Pixel-Link clock\n");
  return ret;
 }

 /* Enable Pixel link Master */
 regmap_set_bits(state->phy_gpr, CSI2SS_PLM_CTRL,
   CSI2SS_PLM_CTRL_ENABLE_PL | CSI2SS_PLM_CTRL_VALID_OVERRIDE);

 /* PHY Enable */
 regmap_clear_bits(state->phy_gpr, CSI2SS_PHY_CTRL,
     CSI2SS_PHY_CTRL_PD | CSI2SS_PLM_CTRL_POLARITY_HIGH);

 /* Release Reset */
 regmap_set_bits(state->phy_gpr, CSI2SS_CTRL_CLK_RESET, CSI2SS_CTRL_CLK_RESET_EN);

 return ret;
}

static void imx8qxp_gpr_disable(struct csi_state *state)
{
 /* Disable Pixel Link */
 regmap_write(state->phy_gpr, CSI2SS_PLM_CTRL, 0x0);

 /* Disable PHY */
 regmap_write(state->phy_gpr, CSI2SS_PHY_CTRL, 0x0);

 regmap_clear_bits(state->phy_gpr, CSI2SS_CTRL_CLK_RESET,
     CSI2SS_CTRL_CLK_RESET_EN);
};

static const struct imx8mq_plat_data imx8qxp_data = {
 .enable = imx8qxp_gpr_enable,
 .disable = imx8qxp_gpr_disable,
 .use_reg_csr = true,
};

static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = {
 /* RAW (Bayer and greyscale) formats. */
 {
  .code = MEDIA_BUS_FMT_SBGGR8_1X8,
  .width = 8,
 }, {
  .code = MEDIA_BUS_FMT_SGBRG8_1X8,
  .width = 8,
 }, {
  .code = MEDIA_BUS_FMT_SGRBG8_1X8,
  .width = 8,
 }, {
  .code = MEDIA_BUS_FMT_SRGGB8_1X8,
  .width = 8,
 }, {
  .code = MEDIA_BUS_FMT_Y8_1X8,
  .width = 8,
 }, {
  .code = MEDIA_BUS_FMT_SBGGR10_1X10,
  .width = 10,
 }, {
  .code = MEDIA_BUS_FMT_SGBRG10_1X10,
  .width = 10,
 }, {
  .code = MEDIA_BUS_FMT_SGRBG10_1X10,
  .width = 10,
 }, {
  .code = MEDIA_BUS_FMT_SRGGB10_1X10,
  .width = 10,
 }, {
  .code = MEDIA_BUS_FMT_Y10_1X10,
  .width = 10,
 }, {
  .code = MEDIA_BUS_FMT_SBGGR12_1X12,
  .width = 12,
 }, {
  .code = MEDIA_BUS_FMT_SGBRG12_1X12,
  .width = 12,
 }, {
  .code = MEDIA_BUS_FMT_SGRBG12_1X12,
  .width = 12,
 }, {
  .code = MEDIA_BUS_FMT_SRGGB12_1X12,
  .width = 12,
 }, {
  .code = MEDIA_BUS_FMT_Y12_1X12,
  .width = 12,
 }, {
  .code = MEDIA_BUS_FMT_SBGGR14_1X14,
  .width = 14,
 }, {
  .code = MEDIA_BUS_FMT_SGBRG14_1X14,
  .width = 14,
 }, {
  .code = MEDIA_BUS_FMT_SGRBG14_1X14,
  .width = 14,
 }, {
  .code = MEDIA_BUS_FMT_SRGGB14_1X14,
  .width = 14,
 },
 /* YUV formats */
 {
  .code = MEDIA_BUS_FMT_YUYV8_1X16,
  .width = 16,
 }, {
  .code = MEDIA_BUS_FMT_UYVY8_1X16,
  .width = 16,
 }
};

static const struct csi2_pix_format *find_csi2_format(u32 code)
{
 unsigned int i;

 for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++)
  if (code == imx8mq_mipi_csi_formats[i].code)
   return &imx8mq_mipi_csi_formats[i];
 return NULL;
}

/* -----------------------------------------------------------------------------
 * Hardware configuration
 */


static inline void imx8mq_mipi_csi_write(struct csi_state *state, u32 reg, u32 val)
{
 writel(val, state->regs + reg);
}

static int imx8mq_mipi_csi_sw_reset(struct csi_state *state)
{
 int ret;

 /*
 * these are most likely self-clearing reset bits. to make it
 * more clear, the reset-imx7 driver should implement the
 * .reset() operation.
 */

 ret = reset_control_assert(state->rst);
 if (ret < 0) {
  dev_err(state->dev, "Failed to assert resets: %d\n", ret);
  return ret;
 }

 return 0;
}

static void imx8mq_mipi_csi_set_params(struct csi_state *state)
{
 int lanes = state->bus.num_data_lanes;

 imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1);
 imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES,
         (0xf << lanes) & 0xf);
 imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK, CSI2RX_IRQ_MASK_ALL);
 /*
 * 0x180 bit 0 controls the Virtual Channel behaviour: when set the
 * interface ignores the Virtual Channel (VC) field in received packets;
 * when cleared it causes the interface to only accept packets whose VC
 * matches the value to which VC is set at offset 0x184.
 */

 imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_VC_IGNORE, 1);
 imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL,
         CSI2RX_SEND_LEVEL);
}

static int imx8mq_mipi_csi_clk_enable(struct csi_state *state)
{
 return clk_bulk_prepare_enable(CSI2_NUM_CLKS, state->clks);
}

static void imx8mq_mipi_csi_clk_disable(struct csi_state *state)
{
 clk_bulk_disable_unprepare(CSI2_NUM_CLKS, state->clks);
}

static int imx8mq_mipi_csi_clk_get(struct csi_state *state)
{
 unsigned int i;

 for (i = 0; i < CSI2_NUM_CLKS; i++)
  state->clks[i].id = imx8mq_mipi_csi_clk_id[i];

 return devm_clk_bulk_get(state->dev, CSI2_NUM_CLKS, state->clks);
}

static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state,
       struct v4l2_subdev_state *sd_state,
       u32 *hs_settle)
{
 struct media_pad *src_pad;
 s64 link_freq;
 u32 lane_rate;
 unsigned long esc_clk_rate;
 u32 min_ths_settle, max_ths_settle, ths_settle_ns, esc_clk_period_ns;
 const struct v4l2_mbus_framefmt *fmt;
 const struct csi2_pix_format *csi2_fmt;

 src_pad = media_entity_remote_source_pad_unique(&sd_state->sd->entity);
 if (IS_ERR(src_pad)) {
  dev_err(state->dev, "can't get source pad of %s (%ld)\n",
   sd_state->sd->name, PTR_ERR(src_pad));
  return PTR_ERR(src_pad);
 }

 /* Calculate the line rate from the pixel rate. */

 fmt = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SINK);
 csi2_fmt = find_csi2_format(fmt->code);

 link_freq = v4l2_get_link_freq(src_pad, csi2_fmt->width,
           state->bus.num_data_lanes * 2);
 if (link_freq < 0) {
  dev_err(state->dev, "Unable to obtain link frequency: %d\n",
   (int)link_freq);
  return link_freq;
 }

 lane_rate = link_freq * 2;
 if (lane_rate < 80000000 || lane_rate > 1500000000) {
  dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate);
  return -EINVAL;
 }

 /*
 * The D-PHY specification requires Ths-settle to be in the range
 * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half
 * the clock period.
 *
 * The Ths-settle value is expressed in the hardware as a multiple of
 * the Esc clock period:
 *
 * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc
 *
 * Due to the one cycle inaccuracy introduced by rounding, the
 * documentation recommends picking a value away from the boundaries.
 * Let's pick the average.
 */

 esc_clk_rate = clk_get_rate(state->clks[CSI2_CLK_ESC].clk);
 if (!esc_clk_rate) {
  dev_err(state->dev, "Could not get esc clock rate.\n");
  return -EINVAL;
 }

 dev_dbg(state->dev, "esc clk rate: %lu\n", esc_clk_rate);
 esc_clk_period_ns = 1000000000 / esc_clk_rate;

 min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000);
 max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000);
 ths_settle_ns = (min_ths_settle + max_ths_settle) / 2;

 *hs_settle = ths_settle_ns / esc_clk_period_ns - 1;

 dev_dbg(state->dev, "lane rate %u Ths_settle %u hs_settle %u\n",
  lane_rate, ths_settle_ns, *hs_settle);

 return 0;
}

static int imx8mq_mipi_csi_start_stream(struct csi_state *state,
     struct v4l2_subdev_state *sd_state)
{
 int ret;
 u32 hs_settle = 0;

 ret = imx8mq_mipi_csi_sw_reset(state);
 if (ret)
  return ret;

 imx8mq_mipi_csi_set_params(state);
 ret = imx8mq_mipi_csi_calc_hs_settle(state, sd_state, &hs_settle);
 if (ret)
  return ret;

 ret = state->pdata->enable(state, hs_settle);
 if (ret)
  return ret;

 return 0;
}

static void imx8mq_mipi_csi_stop_stream(struct csi_state *state)
{
 imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf);

 if (state->pdata->disable)
  state->pdata->disable(state);
}

/* -----------------------------------------------------------------------------
 * V4L2 subdev operations
 */


static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev)
{
 return container_of(sdev, struct csi_state, sd);
}

static int imx8mq_mipi_csi_s_stream(struct v4l2_subdev *sd, int enable)
{
 struct csi_state *state = mipi_sd_to_csi2_state(sd);
 struct v4l2_subdev_state *sd_state;
 int ret = 0;

 if (enable) {
  ret = pm_runtime_resume_and_get(state->dev);
  if (ret < 0)
   return ret;
 }

 mutex_lock(&state->lock);

 if (enable) {
  if (state->state & ST_SUSPENDED) {
   ret = -EBUSY;
   goto unlock;
  }

  sd_state = v4l2_subdev_lock_and_get_active_state(sd);
  ret = imx8mq_mipi_csi_start_stream(state, sd_state);
  v4l2_subdev_unlock_state(sd_state);

  if (ret < 0)
   goto unlock;

  ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1);
  if (ret < 0)
   goto unlock;

  state->state |= ST_STREAMING;
 } else {
  v4l2_subdev_call(state->src_sd, video, s_stream, 0);
  imx8mq_mipi_csi_stop_stream(state);
  state->state &= ~ST_STREAMING;
 }

unlock:
 mutex_unlock(&state->lock);

 if (!enable || ret < 0)
  pm_runtime_put(state->dev);

 return ret;
}

static int imx8mq_mipi_csi_init_state(struct v4l2_subdev *sd,
          struct v4l2_subdev_state *sd_state)
{
 struct v4l2_mbus_framefmt *fmt_sink;
 struct v4l2_mbus_framefmt *fmt_source;

 fmt_sink = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SINK);
 fmt_source = v4l2_subdev_state_get_format(sd_state,
        MIPI_CSI2_PAD_SOURCE);

 fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10;
 fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH;
 fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT;
 fmt_sink->field = V4L2_FIELD_NONE;

 fmt_sink->colorspace = V4L2_COLORSPACE_RAW;
 fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);
 fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);
 fmt_sink->quantization =
  V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,
           fmt_sink->ycbcr_enc);

 *fmt_source = *fmt_sink;

 return 0;
}

static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
       struct v4l2_subdev_state *sd_state,
       struct v4l2_subdev_mbus_code_enum *code)
{
 /*
 * We can't transcode in any way, the source format is identical
 * to the sink format.
 */

 if (code->pad == MIPI_CSI2_PAD_SOURCE) {
  struct v4l2_mbus_framefmt *fmt;

  if (code->index > 0)
   return -EINVAL;

  fmt = v4l2_subdev_state_get_format(sd_state, code->pad);
  code->code = fmt->code;
  return 0;
 }

 if (code->pad != MIPI_CSI2_PAD_SINK)
  return -EINVAL;

 if (code->index >= ARRAY_SIZE(imx8mq_mipi_csi_formats))
  return -EINVAL;

 code->code = imx8mq_mipi_csi_formats[code->index].code;

 return 0;
}

static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd,
       struct v4l2_subdev_state *sd_state,
       struct v4l2_subdev_format *sdformat)
{
 const struct csi2_pix_format *csi2_fmt;
 struct v4l2_mbus_framefmt *fmt;

 /*
 * The device can't transcode in any way, the source format can't be
 * modified.
 */

 if (sdformat->pad == MIPI_CSI2_PAD_SOURCE)
  return v4l2_subdev_get_fmt(sd, sd_state, sdformat);

 if (sdformat->pad != MIPI_CSI2_PAD_SINK)
  return -EINVAL;

 csi2_fmt = find_csi2_format(sdformat->format.code);
 if (!csi2_fmt)
  csi2_fmt = &imx8mq_mipi_csi_formats[0];

 fmt = v4l2_subdev_state_get_format(sd_state, sdformat->pad);

 fmt->code = csi2_fmt->code;
 fmt->width = sdformat->format.width;
 fmt->height = sdformat->format.height;

 sdformat->format = *fmt;

 /* Propagate the format from sink to source. */
 fmt = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SOURCE);
 *fmt = sdformat->format;

 return 0;
}

static const struct v4l2_subdev_video_ops imx8mq_mipi_csi_video_ops = {
 .s_stream = imx8mq_mipi_csi_s_stream,
};

static const struct v4l2_subdev_pad_ops imx8mq_mipi_csi_pad_ops = {
 .enum_mbus_code  = imx8mq_mipi_csi_enum_mbus_code,
 .get_fmt  = v4l2_subdev_get_fmt,
 .set_fmt  = imx8mq_mipi_csi_set_fmt,
};

static const struct v4l2_subdev_ops imx8mq_mipi_csi_subdev_ops = {
 .video = &imx8mq_mipi_csi_video_ops,
 .pad = &imx8mq_mipi_csi_pad_ops,
};

static const struct v4l2_subdev_internal_ops imx8mq_mipi_csi_internal_ops = {
 .init_state  = imx8mq_mipi_csi_init_state,
};

/* -----------------------------------------------------------------------------
 * Media entity operations
 */


static const struct media_entity_operations imx8mq_mipi_csi_entity_ops = {
 .link_validate = v4l2_subdev_link_validate,
 .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
};

/* -----------------------------------------------------------------------------
 * Async subdev notifier
 */


static struct csi_state *
mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n)
{
 return container_of(n, struct csi_state, notifier);
}

static int imx8mq_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier,
     struct v4l2_subdev *sd,
     struct v4l2_async_connection *asd)
{
 struct csi_state *state = mipi_notifier_to_csi2_state(notifier);
 struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK];

 state->src_sd = sd;

 return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED |
            MEDIA_LNK_FL_IMMUTABLE);
}

static const struct v4l2_async_notifier_operations imx8mq_mipi_csi_notify_ops = {
 .bound = imx8mq_mipi_csi_notify_bound,
};

static int imx8mq_mipi_csi_async_register(struct csi_state *state)
{
 struct v4l2_fwnode_endpoint vep = {
  .bus_type = V4L2_MBUS_CSI2_DPHY,
 };
 struct v4l2_async_connection *asd;
 struct fwnode_handle *ep;
 unsigned int i;
 int ret;

 v4l2_async_subdev_nf_init(&state->notifier, &state->sd);

 ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0,
          FWNODE_GRAPH_ENDPOINT_NEXT);
 if (!ep)
  return -ENOTCONN;

 ret = v4l2_fwnode_endpoint_parse(ep, &vep);
 if (ret)
  goto err_parse;

 for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {
  if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {
   dev_err(state->dev,
    "data lanes reordering is not supported");
   ret = -EINVAL;
   goto err_parse;
  }
 }

 state->bus = vep.bus.mipi_csi2;

 dev_dbg(state->dev, "data lanes: %d flags: 0x%08x\n",
  state->bus.num_data_lanes,
  state->bus.flags);

 asd = v4l2_async_nf_add_fwnode_remote(&state->notifier, ep,
           struct v4l2_async_connection);
 if (IS_ERR(asd)) {
  ret = PTR_ERR(asd);
  goto err_parse;
 }

 fwnode_handle_put(ep);

 state->notifier.ops = &imx8mq_mipi_csi_notify_ops;

 ret = v4l2_async_nf_register(&state->notifier);
 if (ret)
  return ret;

 return v4l2_async_register_subdev(&state->sd);

err_parse:
 fwnode_handle_put(ep);

 return ret;
}

/* -----------------------------------------------------------------------------
 * Suspend/resume
 */


static void imx8mq_mipi_csi_pm_suspend(struct device *dev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);

 mutex_lock(&state->lock);

 if (state->state & ST_POWERED) {
  imx8mq_mipi_csi_stop_stream(state);
  imx8mq_mipi_csi_clk_disable(state);
  state->state &= ~ST_POWERED;
 }

 mutex_unlock(&state->lock);
}

static int imx8mq_mipi_csi_pm_resume(struct device *dev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);
 struct v4l2_subdev_state *sd_state;
 int ret = 0;

 mutex_lock(&state->lock);

 if (!(state->state & ST_POWERED)) {
  state->state |= ST_POWERED;
  ret = imx8mq_mipi_csi_clk_enable(state);
 }
 if (state->state & ST_STREAMING) {
  sd_state = v4l2_subdev_lock_and_get_active_state(sd);
  ret = imx8mq_mipi_csi_start_stream(state, sd_state);
  v4l2_subdev_unlock_state(sd_state);
  if (ret)
   goto unlock;
 }

 state->state &= ~ST_SUSPENDED;

unlock:
 mutex_unlock(&state->lock);

 return ret ? -EAGAIN : 0;
}

static int imx8mq_mipi_csi_suspend(struct device *dev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);

 imx8mq_mipi_csi_pm_suspend(dev);

 state->state |= ST_SUSPENDED;

 return 0;
}

static int imx8mq_mipi_csi_resume(struct device *dev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);

 if (!(state->state & ST_SUSPENDED))
  return 0;

 return imx8mq_mipi_csi_pm_resume(dev);
}

static int imx8mq_mipi_csi_runtime_suspend(struct device *dev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);
 int ret;

 imx8mq_mipi_csi_pm_suspend(dev);

 ret = icc_set_bw(state->icc_path, 0, 0);
 if (ret)
  dev_err(dev, "icc_set_bw failed with %d\n", ret);

 return ret;
}

static int imx8mq_mipi_csi_runtime_resume(struct device *dev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);
 int ret;

 ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw);
 if (ret) {
  dev_err(dev, "icc_set_bw failed with %d\n", ret);
  return ret;
 }

 return imx8mq_mipi_csi_pm_resume(dev);
}

static const struct dev_pm_ops imx8mq_mipi_csi_pm_ops = {
 RUNTIME_PM_OPS(imx8mq_mipi_csi_runtime_suspend,
         imx8mq_mipi_csi_runtime_resume, NULL)
 SYSTEM_SLEEP_PM_OPS(imx8mq_mipi_csi_suspend, imx8mq_mipi_csi_resume)
};

/* -----------------------------------------------------------------------------
 * Probe/remove & platform driver
 */


static int imx8mq_mipi_csi_subdev_init(struct csi_state *state)
{
 struct v4l2_subdev *sd = &state->sd;
 int ret;

 v4l2_subdev_init(sd, &imx8mq_mipi_csi_subdev_ops);
 sd->internal_ops = &imx8mq_mipi_csi_internal_ops;
 sd->owner = THIS_MODULE;
 snprintf(sd->name, sizeof(sd->name), "%s %s",
   MIPI_CSI2_SUBDEV_NAME, dev_name(state->dev));

 sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;

 sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
 sd->entity.ops = &imx8mq_mipi_csi_entity_ops;

 sd->dev = state->dev;

 state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK
      | MEDIA_PAD_FL_MUST_CONNECT;
 state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE
        | MEDIA_PAD_FL_MUST_CONNECT;
 ret = media_entity_pads_init(&sd->entity, MIPI_CSI2_PADS_NUM,
         state->pads);
 if (ret)
  return ret;

 ret = v4l2_subdev_init_finalize(sd);
 if (ret) {
  media_entity_cleanup(&sd->entity);
  return ret;
 }

 return 0;
}

static void imx8mq_mipi_csi_release_icc(struct platform_device *pdev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);

 icc_put(state->icc_path);
}

static int imx8mq_mipi_csi_init_icc(struct platform_device *pdev)
{
 struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);

 /* Optional interconnect request */
 state->icc_path = of_icc_get(&pdev->dev, "dram");
 if (IS_ERR_OR_NULL(state->icc_path))
  return PTR_ERR_OR_ZERO(state->icc_path);

 state->icc_path_bw = MBps_to_icc(700);

 return 0;
}

static int imx8mq_mipi_csi_parse_dt(struct csi_state *state)
{
 struct device *dev = state->dev;
 struct device_node *np = state->dev->of_node;
 struct device_node *node;
 phandle ph;
 u32 out_val[2];
 int ret = 0;

 state->rst = devm_reset_control_array_get_exclusive(dev);
 if (IS_ERR(state->rst)) {
  dev_err(dev, "Failed to get reset: %pe\n", state->rst);
  return PTR_ERR(state->rst);
 }

 if (state->pdata->use_reg_csr) {
  const struct regmap_config regmap_config = {
   .reg_bits = 32,
   .val_bits = 32,
   .reg_stride = 4,
  };
  void __iomem *base;

  base = devm_platform_ioremap_resource(to_platform_device(dev), 1);
  if (IS_ERR(base))
   return dev_err_probe(dev, PTR_ERR(base), "Missing CSR register\n");

  state->phy_gpr = devm_regmap_init_mmio(dev, base, ®map_config);
  if (IS_ERR(state->phy_gpr))
   return dev_err_probe(dev, PTR_ERR(state->phy_gpr),
          "Failed to init CSI MMIO regmap\n");
  return 0;
 }

 ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val,
      ARRAY_SIZE(out_val));
 if (ret) {
  dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n", ret);
  return ret;
 }

 ph = *out_val;

 node = of_find_node_by_phandle(ph);
 if (!node) {
  dev_err(dev, "Error finding node by phandle\n");
  return -ENODEV;
 }
 state->phy_gpr = syscon_node_to_regmap(node);
 of_node_put(node);
 if (IS_ERR(state->phy_gpr)) {
  dev_err(dev, "failed to get gpr regmap: %pe\n", state->phy_gpr);
  return PTR_ERR(state->phy_gpr);
 }

 state->phy_gpr_reg = out_val[1];
 dev_dbg(dev, "phy gpr register set to 0x%x\n", state->phy_gpr_reg);

 return ret;
}

static int imx8mq_mipi_csi_probe(struct platform_device *pdev)
{
 struct device *dev = &pdev->dev;
 struct csi_state *state;
 int ret;

 state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
 if (!state)
  return -ENOMEM;

 state->dev = dev;

 state->pdata = of_device_get_match_data(dev);

 ret = imx8mq_mipi_csi_parse_dt(state);
 if (ret < 0) {
  dev_err(dev, "Failed to parse device tree: %d\n", ret);
  return ret;
 }

 /* Acquire resources. */
 state->regs = devm_platform_ioremap_resource(pdev, 0);
 if (IS_ERR(state->regs))
  return PTR_ERR(state->regs);

 ret = imx8mq_mipi_csi_clk_get(state);
 if (ret < 0)
  return ret;

 platform_set_drvdata(pdev, &state->sd);

 mutex_init(&state->lock);

 ret = imx8mq_mipi_csi_subdev_init(state);
 if (ret < 0)
  goto mutex;

 ret = imx8mq_mipi_csi_init_icc(pdev);
 if (ret)
  goto mutex;

 /* Enable runtime PM. */
 pm_runtime_enable(dev);
 if (!pm_runtime_enabled(dev)) {
  ret = imx8mq_mipi_csi_runtime_resume(dev);
  if (ret < 0)
   goto icc;
 }

 ret = imx8mq_mipi_csi_async_register(state);
 if (ret < 0)
  goto cleanup;

 return 0;

cleanup:
 pm_runtime_disable(&pdev->dev);
 imx8mq_mipi_csi_runtime_suspend(&pdev->dev);

 media_entity_cleanup(&state->sd.entity);
 v4l2_subdev_cleanup(&state->sd);
 v4l2_async_nf_unregister(&state->notifier);
 v4l2_async_nf_cleanup(&state->notifier);
 v4l2_async_unregister_subdev(&state->sd);
icc:
 imx8mq_mipi_csi_release_icc(pdev);
mutex:
 mutex_destroy(&state->lock);

 return ret;
}

static void imx8mq_mipi_csi_remove(struct platform_device *pdev)
{
 struct v4l2_subdev *sd = platform_get_drvdata(pdev);
 struct csi_state *state = mipi_sd_to_csi2_state(sd);

 v4l2_async_nf_unregister(&state->notifier);
 v4l2_async_nf_cleanup(&state->notifier);
 v4l2_async_unregister_subdev(&state->sd);

 pm_runtime_disable(&pdev->dev);
 imx8mq_mipi_csi_runtime_suspend(&pdev->dev);
 media_entity_cleanup(&state->sd.entity);
 v4l2_subdev_cleanup(&state->sd);
 mutex_destroy(&state->lock);
 pm_runtime_set_suspended(&pdev->dev);
 imx8mq_mipi_csi_release_icc(pdev);
}

static const struct of_device_id imx8mq_mipi_csi_of_match[] = {
 { .compatible = "fsl,imx8mq-mipi-csi2", .data = &imx8mq_data },
 { .compatible = "fsl,imx8qxp-mipi-csi2", .data = &imx8qxp_data },
 { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match);

static struct platform_driver imx8mq_mipi_csi_driver = {
 .probe  = imx8mq_mipi_csi_probe,
 .remove  = imx8mq_mipi_csi_remove,
 .driver  = {
  .of_match_table = imx8mq_mipi_csi_of_match,
  .name  = MIPI_CSI2_DRIVER_NAME,
  .pm  = pm_ptr(&imx8mq_mipi_csi_pm_ops),
 },
};

module_platform_driver(imx8mq_mipi_csi_driver);

MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver");
MODULE_AUTHOR("Martin Kepplinger ");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:imx8mq-mipi-csi2");

Messung V0.5
C=97 H=93 G=94

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.