diff options
Diffstat (limited to 'drivers/gpu/ipu-v3/ipu-csi.c')
| -rw-r--r-- | drivers/gpu/ipu-v3/ipu-csi.c | 319 |
1 files changed, 146 insertions, 173 deletions
diff --git a/drivers/gpu/ipu-v3/ipu-csi.c b/drivers/gpu/ipu-v3/ipu-csi.c index 24e12b87a0cb..d576b7d28437 100644 --- a/drivers/gpu/ipu-v3/ipu-csi.c +++ b/drivers/gpu/ipu-v3/ipu-csi.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2012-2014 Mentor Graphics Inc. * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. */ #include <linux/export.h> #include <linux/module.h> @@ -195,43 +186,21 @@ static inline void ipu_csi_write(struct ipu_csi *csi, u32 value, } /* - * Set mclk division ratio for generating test mode mclk. Only used - * for test generator. - */ -static int ipu_csi_set_testgen_mclk(struct ipu_csi *csi, u32 pixel_clk, - u32 ipu_clk) -{ - u32 temp; - int div_ratio; - - div_ratio = (ipu_clk / pixel_clk) - 1; - - if (div_ratio > 0xFF || div_ratio < 0) { - dev_err(csi->ipu->dev, - "value of pixel_clk extends normal range\n"); - return -EINVAL; - } - - temp = ipu_csi_read(csi, CSI_SENS_CONF); - temp &= ~CSI_SENS_CONF_DIVRATIO_MASK; - ipu_csi_write(csi, temp | (div_ratio << CSI_SENS_CONF_DIVRATIO_SHIFT), - CSI_SENS_CONF); - - return 0; -} - -/* * Find the CSI data format and data width for the given V4L2 media * bus pixel format code. */ -static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code) +static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code, + enum v4l2_mbus_type mbus_type) { switch (mbus_code) { case MEDIA_BUS_FMT_BGR565_2X8_BE: case MEDIA_BUS_FMT_BGR565_2X8_LE: case MEDIA_BUS_FMT_RGB565_2X8_BE: case MEDIA_BUS_FMT_RGB565_2X8_LE: - cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; + if (mbus_type == V4L2_MBUS_CSI2_DPHY) + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; + else + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; cfg->mipi_dt = MIPI_DT_RGB565; cfg->data_width = IPU_CSI_DATA_WIDTH_8; break; @@ -247,6 +216,12 @@ static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code) cfg->mipi_dt = MIPI_DT_RGB555; cfg->data_width = IPU_CSI_DATA_WIDTH_8; break; + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_BGR888_1X24: + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + cfg->mipi_dt = MIPI_DT_RGB888; + cfg->data_width = IPU_CSI_DATA_WIDTH_8; + break; case MEDIA_BUS_FMT_UYVY8_2X8: cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; cfg->mipi_dt = MIPI_DT_YUV422; @@ -258,10 +233,24 @@ static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code) cfg->data_width = IPU_CSI_DATA_WIDTH_8; break; case MEDIA_BUS_FMT_UYVY8_1X16: + if (mbus_type == V4L2_MBUS_BT656) { + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; + cfg->data_width = IPU_CSI_DATA_WIDTH_8; + } else { + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; + cfg->data_width = IPU_CSI_DATA_WIDTH_16; + } + cfg->mipi_dt = MIPI_DT_YUV422; + break; case MEDIA_BUS_FMT_YUYV8_1X16: - cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; + if (mbus_type == V4L2_MBUS_BT656) { + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; + cfg->data_width = IPU_CSI_DATA_WIDTH_8; + } else { + cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; + cfg->data_width = IPU_CSI_DATA_WIDTH_16; + } cfg->mipi_dt = MIPI_DT_YUV422; - cfg->data_width = IPU_CSI_DATA_WIDTH_16; break; case MEDIA_BUS_FMT_SBGGR8_1X8: case MEDIA_BUS_FMT_SGBRG8_1X8: @@ -288,6 +277,7 @@ static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code) case MEDIA_BUS_FMT_SGBRG10_1X10: case MEDIA_BUS_FMT_SGRBG10_1X10: case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_Y10_1X10: cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; cfg->mipi_dt = MIPI_DT_RAW10; cfg->data_width = IPU_CSI_DATA_WIDTH_10; @@ -296,6 +286,7 @@ static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code) case MEDIA_BUS_FMT_SGBRG12_1X12: case MEDIA_BUS_FMT_SGRBG12_1X12: case MEDIA_BUS_FMT_SRGGB12_1X12: + case MEDIA_BUS_FMT_Y12_1X12: cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; cfg->mipi_dt = MIPI_DT_RAW12; cfg->data_width = IPU_CSI_DATA_WIDTH_12; @@ -313,36 +304,57 @@ static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code) return 0; } +/* translate alternate field mode based on given standard */ +static inline enum v4l2_field +ipu_csi_translate_field(enum v4l2_field field, v4l2_std_id std) +{ + return (field != V4L2_FIELD_ALTERNATE) ? field : + ((std & V4L2_STD_525_60) ? + V4L2_FIELD_SEQ_BT : V4L2_FIELD_SEQ_TB); +} + /* * Fill a CSI bus config struct from mbus_config and mbus_framefmt. */ -static void fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg, - struct v4l2_mbus_config *mbus_cfg, - struct v4l2_mbus_framefmt *mbus_fmt) +static int fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg, + const struct v4l2_mbus_config *mbus_cfg, + const struct v4l2_mbus_framefmt *mbus_fmt) { + int ret, is_bt1120; + memset(csicfg, 0, sizeof(*csicfg)); - mbus_code_to_bus_cfg(csicfg, mbus_fmt->code); + ret = mbus_code_to_bus_cfg(csicfg, mbus_fmt->code, mbus_cfg->type); + if (ret < 0) + return ret; switch (mbus_cfg->type) { case V4L2_MBUS_PARALLEL: csicfg->ext_vsync = 1; - csicfg->vsync_pol = (mbus_cfg->flags & + csicfg->vsync_pol = (mbus_cfg->bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) ? 1 : 0; - csicfg->hsync_pol = (mbus_cfg->flags & + csicfg->hsync_pol = (mbus_cfg->bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) ? 1 : 0; - csicfg->pixclk_pol = (mbus_cfg->flags & + csicfg->pixclk_pol = (mbus_cfg->bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) ? 1 : 0; csicfg->clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; break; case V4L2_MBUS_BT656: csicfg->ext_vsync = 0; - if (V4L2_FIELD_HAS_BOTH(mbus_fmt->field)) - csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; + /* UYVY10_1X20 etc. should be supported as well */ + is_bt1120 = mbus_fmt->code == MEDIA_BUS_FMT_UYVY8_1X16 || + mbus_fmt->code == MEDIA_BUS_FMT_YUYV8_1X16; + if (V4L2_FIELD_HAS_BOTH(mbus_fmt->field) || + mbus_fmt->field == V4L2_FIELD_ALTERNATE) + csicfg->clk_mode = is_bt1120 ? + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR : + IPU_CSI_CLK_MODE_CCIR656_INTERLACED; else - csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE; + csicfg->clk_mode = is_bt1120 ? + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR : + IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE; break; - case V4L2_MBUS_CSI2: + case V4L2_MBUS_CSI2_DPHY: /* * MIPI CSI-2 requires non gated clock mode, all other * parameters are not applicable for MIPI CSI-2 bus. @@ -353,21 +365,80 @@ static void fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg, /* will never get here, keep compiler quiet */ break; } + + return 0; +} + +static int +ipu_csi_set_bt_interlaced_codes(struct ipu_csi *csi, + const struct v4l2_mbus_framefmt *infmt, + const struct v4l2_mbus_framefmt *outfmt, + v4l2_std_id std) +{ + enum v4l2_field infield, outfield; + bool swap_fields; + + /* get translated field type of input and output */ + infield = ipu_csi_translate_field(infmt->field, std); + outfield = ipu_csi_translate_field(outfmt->field, std); + + /* + * Write the H-V-F codes the CSI will match against the + * incoming data for start/end of active and blanking + * field intervals. If input and output field types are + * sequential but not the same (one is SEQ_BT and the other + * is SEQ_TB), swap the F-bit so that the CSI will capture + * field 1 lines before field 0 lines. + */ + swap_fields = (V4L2_FIELD_IS_SEQUENTIAL(infield) && + V4L2_FIELD_IS_SEQUENTIAL(outfield) && + infield != outfield); + + if (!swap_fields) { + /* + * Field0BlankEnd = 110, Field0BlankStart = 010 + * Field0ActiveEnd = 100, Field0ActiveStart = 000 + * Field1BlankEnd = 111, Field1BlankStart = 011 + * Field1ActiveEnd = 101, Field1ActiveStart = 001 + */ + ipu_csi_write(csi, 0x40596 | CSI_CCIR_ERR_DET_EN, + CSI_CCIR_CODE_1); + ipu_csi_write(csi, 0xD07DF, CSI_CCIR_CODE_2); + } else { + dev_dbg(csi->ipu->dev, "capture field swap\n"); + + /* same as above but with F-bit inverted */ + ipu_csi_write(csi, 0xD07DF | CSI_CCIR_ERR_DET_EN, + CSI_CCIR_CODE_1); + ipu_csi_write(csi, 0x40596, CSI_CCIR_CODE_2); + } + + ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); + + return 0; } + int ipu_csi_init_interface(struct ipu_csi *csi, - struct v4l2_mbus_config *mbus_cfg, - struct v4l2_mbus_framefmt *mbus_fmt) + const struct v4l2_mbus_config *mbus_cfg, + const struct v4l2_mbus_framefmt *infmt, + const struct v4l2_mbus_framefmt *outfmt) { struct ipu_csi_bus_config cfg; unsigned long flags; u32 width, height, data = 0; + v4l2_std_id std; + int ret; - fill_csi_bus_cfg(&cfg, mbus_cfg, mbus_fmt); + ret = fill_csi_bus_cfg(&cfg, mbus_cfg, infmt); + if (ret < 0) + return ret; /* set default sensor frame width and height */ - width = mbus_fmt->width; - height = mbus_fmt->height; + width = infmt->width; + height = infmt->height; + if (infmt->field == V4L2_FIELD_ALTERNATE) + height *= 2; /* Set the CSI_SENS_CONF register remaining fields */ data |= cfg.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | @@ -394,42 +465,22 @@ int ipu_csi_init_interface(struct ipu_csi *csi, ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); break; case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: - if (mbus_fmt->width == 720 && mbus_fmt->height == 576) { - /* - * PAL case - * - * Field0BlankEnd = 0x6, Field0BlankStart = 0x2, - * Field0ActiveEnd = 0x4, Field0ActiveStart = 0 - * Field1BlankEnd = 0x7, Field1BlankStart = 0x3, - * Field1ActiveEnd = 0x5, Field1ActiveStart = 0x1 - */ - height = 625; /* framelines for PAL */ - - ipu_csi_write(csi, 0x40596 | CSI_CCIR_ERR_DET_EN, - CSI_CCIR_CODE_1); - ipu_csi_write(csi, 0xD07DF, CSI_CCIR_CODE_2); - ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); - } else if (mbus_fmt->width == 720 && mbus_fmt->height == 480) { - /* - * NTSC case - * - * Field0BlankEnd = 0x7, Field0BlankStart = 0x3, - * Field0ActiveEnd = 0x5, Field0ActiveStart = 0x1 - * Field1BlankEnd = 0x6, Field1BlankStart = 0x2, - * Field1ActiveEnd = 0x4, Field1ActiveStart = 0 - */ - height = 525; /* framelines for NTSC */ - - ipu_csi_write(csi, 0xD07DF | CSI_CCIR_ERR_DET_EN, - CSI_CCIR_CODE_1); - ipu_csi_write(csi, 0x40596, CSI_CCIR_CODE_2); - ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); + if (width == 720 && height == 480) { + std = V4L2_STD_NTSC; + height = 525; + } else if (width == 720 && height == 576) { + std = V4L2_STD_PAL; + height = 625; } else { dev_err(csi->ipu->dev, - "Unsupported CCIR656 interlaced video mode\n"); - spin_unlock_irqrestore(&csi->lock, flags); - return -EINVAL; + "Unsupported interlaced video mode\n"); + ret = -EINVAL; + goto out_unlock; } + + ret = ipu_csi_set_bt_interlaced_codes(csi, infmt, outfmt, std); + if (ret) + goto out_unlock; break; case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: @@ -454,62 +505,13 @@ int ipu_csi_init_interface(struct ipu_csi *csi, dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", ipu_csi_read(csi, CSI_ACT_FRM_SIZE)); +out_unlock: spin_unlock_irqrestore(&csi->lock, flags); - return 0; + return ret; } EXPORT_SYMBOL_GPL(ipu_csi_init_interface); -bool ipu_csi_is_interlaced(struct ipu_csi *csi) -{ - unsigned long flags; - u32 sensor_protocol; - - spin_lock_irqsave(&csi->lock, flags); - sensor_protocol = - (ipu_csi_read(csi, CSI_SENS_CONF) & - CSI_SENS_CONF_SENS_PRTCL_MASK) >> - CSI_SENS_CONF_SENS_PRTCL_SHIFT; - spin_unlock_irqrestore(&csi->lock, flags); - - switch (sensor_protocol) { - case IPU_CSI_CLK_MODE_GATED_CLK: - case IPU_CSI_CLK_MODE_NONGATED_CLK: - case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE: - case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: - case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: - return false; - case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: - case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR: - case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR: - return true; - default: - dev_err(csi->ipu->dev, - "CSI %d sensor protocol unsupported\n", csi->id); - return false; - } -} -EXPORT_SYMBOL_GPL(ipu_csi_is_interlaced); - -void ipu_csi_get_window(struct ipu_csi *csi, struct v4l2_rect *w) -{ - unsigned long flags; - u32 reg; - - spin_lock_irqsave(&csi->lock, flags); - - reg = ipu_csi_read(csi, CSI_ACT_FRM_SIZE); - w->width = (reg & 0xFFFF) + 1; - w->height = (reg >> 16 & 0xFFFF) + 1; - - reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); - w->left = (reg & CSI_HSC_MASK) >> CSI_HSC_SHIFT; - w->top = (reg & CSI_VSC_MASK) >> CSI_VSC_SHIFT; - - spin_unlock_irqrestore(&csi->lock, flags); -} -EXPORT_SYMBOL_GPL(ipu_csi_get_window); - void ipu_csi_set_window(struct ipu_csi *csi, struct v4l2_rect *w) { unsigned long flags; @@ -546,49 +548,20 @@ void ipu_csi_set_downsize(struct ipu_csi *csi, bool horiz, bool vert) } EXPORT_SYMBOL_GPL(ipu_csi_set_downsize); -void ipu_csi_set_test_generator(struct ipu_csi *csi, bool active, - u32 r_value, u32 g_value, u32 b_value, - u32 pix_clk) -{ - unsigned long flags; - u32 ipu_clk = clk_get_rate(csi->clk_ipu); - u32 temp; - - spin_lock_irqsave(&csi->lock, flags); - - temp = ipu_csi_read(csi, CSI_TST_CTRL); - - if (!active) { - temp &= ~CSI_TEST_GEN_MODE_EN; - ipu_csi_write(csi, temp, CSI_TST_CTRL); - } else { - /* Set sensb_mclk div_ratio */ - ipu_csi_set_testgen_mclk(csi, pix_clk, ipu_clk); - - temp &= ~(CSI_TEST_GEN_R_MASK | CSI_TEST_GEN_G_MASK | - CSI_TEST_GEN_B_MASK); - temp |= CSI_TEST_GEN_MODE_EN; - temp |= (r_value << CSI_TEST_GEN_R_SHIFT) | - (g_value << CSI_TEST_GEN_G_SHIFT) | - (b_value << CSI_TEST_GEN_B_SHIFT); - ipu_csi_write(csi, temp, CSI_TST_CTRL); - } - - spin_unlock_irqrestore(&csi->lock, flags); -} -EXPORT_SYMBOL_GPL(ipu_csi_set_test_generator); - int ipu_csi_set_mipi_datatype(struct ipu_csi *csi, u32 vc, struct v4l2_mbus_framefmt *mbus_fmt) { struct ipu_csi_bus_config cfg; unsigned long flags; u32 temp; + int ret; if (vc > 3) return -EINVAL; - mbus_code_to_bus_cfg(&cfg, mbus_fmt->code); + ret = mbus_code_to_bus_cfg(&cfg, mbus_fmt->code, V4L2_MBUS_CSI2_DPHY); + if (ret < 0) + return ret; spin_lock_irqsave(&csi->lock, flags); |
