media: gpu: ipu-csi: Swap fields according to input/output field types
The function ipu_csi_init_interface() was inverting the F-bit for NTSC case, in the CCIR_CODE_1/2 registers. The result being that for NTSC bottom-top field order, the CSI would swap fields and capture in top-bottom order. Instead, base field swap on the field order of the input to the CSI, and the field order of the requested output. If the input/output fields are sequential but different, swap fields, otherwise do not swap. This requires passing both the input and output mbus frame formats to ipu_csi_init_interface(). Move this code to a new private function ipu_csi_set_bt_interlaced_codes() that programs the CCIR_CODE_1/2 registers for interlaced BT.656 (and possibly interlaced BT.1120 in the future). When detecting input video standard from the input frame width/height, make sure to double height if input field type is alternate, since in that case input height only includes lines for one field. Signed-off-by: Steve Longerbeam <slongerbeam@gmail.com> Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
This commit is contained in:
parent
1c3721b1f2
commit
fc8c723852
|
@ -325,12 +325,21 @@ 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 int fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg,
|
||||
struct v4l2_mbus_config *mbus_cfg,
|
||||
struct v4l2_mbus_framefmt *mbus_fmt)
|
||||
const struct v4l2_mbus_config *mbus_cfg,
|
||||
const struct v4l2_mbus_framefmt *mbus_fmt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -374,22 +383,76 @@ static int fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg,
|
|||
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;
|
||||
|
||||
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 |
|
||||
|
@ -416,42 +479,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:
|
||||
|
@ -476,9 +519,10 @@ 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);
|
||||
|
||||
|
|
|
@ -679,12 +679,7 @@ static int csi_setup(struct csi_priv *priv)
|
|||
priv->upstream_ep.bus.parallel.flags :
|
||||
priv->upstream_ep.bus.mipi_csi2.flags;
|
||||
|
||||
/*
|
||||
* we need to pass input frame to CSI interface, but
|
||||
* with translated field type from output format
|
||||
*/
|
||||
if_fmt = *infmt;
|
||||
if_fmt.field = outfmt->field;
|
||||
crop = priv->crop;
|
||||
|
||||
/*
|
||||
|
@ -702,7 +697,7 @@ static int csi_setup(struct csi_priv *priv)
|
|||
priv->crop.width == 2 * priv->compose.width,
|
||||
priv->crop.height == 2 * priv->compose.height);
|
||||
|
||||
ipu_csi_init_interface(priv->csi, &mbus_cfg, &if_fmt);
|
||||
ipu_csi_init_interface(priv->csi, &mbus_cfg, &if_fmt, outfmt);
|
||||
|
||||
ipu_csi_set_dest(priv->csi, priv->dest);
|
||||
|
||||
|
|
|
@ -354,8 +354,9 @@ int ipu_prg_channel_configure(struct ipuv3_channel *ipu_chan,
|
|||
*/
|
||||
struct ipu_csi;
|
||||
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);
|
||||
bool ipu_csi_is_interlaced(struct ipu_csi *csi);
|
||||
void ipu_csi_get_window(struct ipu_csi *csi, struct v4l2_rect *w);
|
||||
void ipu_csi_set_window(struct ipu_csi *csi, struct v4l2_rect *w);
|
||||
|
|
Loading…
Reference in New Issue