Commit bcb4e0ef authored by Laurent Pinchart's avatar Laurent Pinchart Committed by Mauro Carvalho Chehab

[media] omap3isp: ccdc: Support the interlaced field orders at the CCDC output

The CCDC can interleave fields into a single buffer when writing to
memory. Support it.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Tested-by: default avatarEnrico Butera <ebutera@users.sourceforge.net>
Acked-by: default avatarSakari Ailus <sakari.ailus@iki.fi>
Signed-off-by: default avatarMauro Carvalho Chehab <m.chehab@samsung.com>
parent 9a36d8ed
...@@ -863,52 +863,51 @@ static void ccdc_enable_vp(struct isp_ccdc_device *ccdc, u8 enable) ...@@ -863,52 +863,51 @@ static void ccdc_enable_vp(struct isp_ccdc_device *ccdc, u8 enable)
/* /*
* ccdc_config_outlineoffset - Configure memory saving output line offset * ccdc_config_outlineoffset - Configure memory saving output line offset
* @ccdc: Pointer to ISP CCDC device. * @ccdc: Pointer to ISP CCDC device.
* @offset: Address offset to start a new line. Must be twice the * @bpl: Number of bytes per line when stored in memory.
* Output width and aligned on 32 byte boundary * @field: Field order when storing interlaced formats in memory.
* @oddeven: Specifies the odd/even line pattern to be chosen to store the
* output.
* @numlines: Set the value 0-3 for +1-4lines, 4-7 for -1-4lines.
* *
* - Configures the output line offset when stored in memory * Configure the offsets for the line output control:
* - Sets the odd/even line pattern to store the output *
* (EVENEVEN (1), ODDEVEN (2), EVENODD (3), ODDODD (4)) * - The horizontal line offset is defined as the number of bytes between the
* - Configures the number of even and odd line fields in case of rearranging * start of two consecutive lines in memory. Set it to the given bytes per
* the lines. * line value.
*
* - The field offset value is defined as the number of lines to offset the
* start of the field identified by FID = 1. Set it to one.
*
* - The line offset values are defined as the number of lines (as defined by
* the horizontal line offset) between the start of two consecutive lines for
* all combinations of odd/even lines in odd/even fields. When interleaving
* fields set them all to two lines, and to one line otherwise.
*/ */
static void ccdc_config_outlineoffset(struct isp_ccdc_device *ccdc, static void ccdc_config_outlineoffset(struct isp_ccdc_device *ccdc,
u32 offset, u8 oddeven, u8 numlines) unsigned int bpl,
enum v4l2_field field)
{ {
struct isp_device *isp = to_isp_device(ccdc); struct isp_device *isp = to_isp_device(ccdc);
u32 sdofst = 0;
isp_reg_writel(isp, offset & 0xffff, isp_reg_writel(isp, bpl & 0xffff, OMAP3_ISP_IOMEM_CCDC,
OMAP3_ISP_IOMEM_CCDC, ISPCCDC_HSIZE_OFF); ISPCCDC_HSIZE_OFF);
isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
ISPCCDC_SDOFST_FINV);
isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, switch (field) {
ISPCCDC_SDOFST_FOFST_4L); case V4L2_FIELD_INTERLACED_TB:
case V4L2_FIELD_INTERLACED_BT:
switch (oddeven) { /* When interleaving fields in memory offset field one by one
case EVENEVEN: * line and set the line offset to two lines.
isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, */
(numlines & 0x7) << ISPCCDC_SDOFST_LOFST0_SHIFT); sdofst |= (1 << ISPCCDC_SDOFST_LOFST0_SHIFT)
break; | (1 << ISPCCDC_SDOFST_LOFST1_SHIFT)
case ODDEVEN: | (1 << ISPCCDC_SDOFST_LOFST2_SHIFT)
isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, | (1 << ISPCCDC_SDOFST_LOFST3_SHIFT);
(numlines & 0x7) << ISPCCDC_SDOFST_LOFST1_SHIFT);
break;
case EVENODD:
isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
(numlines & 0x7) << ISPCCDC_SDOFST_LOFST2_SHIFT);
break;
case ODDODD:
isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
(numlines & 0x7) << ISPCCDC_SDOFST_LOFST3_SHIFT);
break; break;
default: default:
/* In all other cases set the line offsets to one line. */
break; break;
} }
isp_reg_writel(isp, sdofst, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST);
} }
/* /*
...@@ -1204,7 +1203,17 @@ static void ccdc_configure(struct isp_ccdc_device *ccdc) ...@@ -1204,7 +1203,17 @@ static void ccdc_configure(struct isp_ccdc_device *ccdc)
<< ISPCCDC_VERT_LINES_NLV_SHIFT, << ISPCCDC_VERT_LINES_NLV_SHIFT,
OMAP3_ISP_IOMEM_CCDC, ISPCCDC_VERT_LINES); OMAP3_ISP_IOMEM_CCDC, ISPCCDC_VERT_LINES);
ccdc_config_outlineoffset(ccdc, ccdc->video_out.bpl_value, 0, 0); ccdc_config_outlineoffset(ccdc, ccdc->video_out.bpl_value,
format->field);
/* When interleaving fields enable processing of the field input signal.
* This will cause the line output control module to apply the field
* offset to field 1.
*/
if (ccdc->formats[CCDC_PAD_SINK].field == V4L2_FIELD_ALTERNATE &&
(format->field == V4L2_FIELD_INTERLACED_TB ||
format->field == V4L2_FIELD_INTERLACED_BT))
syn_mode |= ISPCCDC_SYN_MODE_FLDMODE;
/* The CCDC outputs data in UYVY order by default. Swap bytes to get /* The CCDC outputs data in UYVY order by default. Swap bytes to get
* YUYV. * YUYV.
...@@ -1484,6 +1493,7 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc) ...@@ -1484,6 +1493,7 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc)
struct isp_pipeline *pipe = to_isp_pipeline(&ccdc->subdev.entity); struct isp_pipeline *pipe = to_isp_pipeline(&ccdc->subdev.entity);
struct isp_device *isp = to_isp_device(ccdc); struct isp_device *isp = to_isp_device(ccdc);
struct isp_buffer *buffer; struct isp_buffer *buffer;
enum v4l2_field field;
/* The CCDC generates VD0 interrupts even when disabled (the datasheet /* The CCDC generates VD0 interrupts even when disabled (the datasheet
* doesn't explicitly state if that's supposed to happen or not, so it * doesn't explicitly state if that's supposed to happen or not, so it
...@@ -1503,17 +1513,12 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc) ...@@ -1503,17 +1513,12 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc)
return 1; return 1;
} }
/* When capturing fields in alternate order read the current field /* Read the current field identifier. */
* identifier and store it in the pipeline. field = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE)
*/ & ISPCCDC_SYN_MODE_FLDSTAT
if (ccdc->formats[CCDC_PAD_SOURCE_OF].field == V4L2_FIELD_ALTERNATE) { ? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP;
u32 syn_mode = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC,
ISPCCDC_SYN_MODE);
pipe->field = syn_mode & ISPCCDC_SYN_MODE_FLDSTAT
? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP;
}
/* Wait for the CCDC to become idle. */
if (ccdc_sbl_wait_idle(ccdc, 1000)) { if (ccdc_sbl_wait_idle(ccdc, 1000)) {
dev_info(isp->dev, "CCDC won't become idle!\n"); dev_info(isp->dev, "CCDC won't become idle!\n");
isp->crashed |= 1U << ccdc->subdev.entity.id; isp->crashed |= 1U << ccdc->subdev.entity.id;
...@@ -1521,6 +1526,28 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc) ...@@ -1521,6 +1526,28 @@ static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc)
return 0; return 0;
} }
switch (ccdc->formats[CCDC_PAD_SOURCE_OF].field) {
case V4L2_FIELD_ALTERNATE:
/* When capturing fields in alternate order store the current
* field identifier in the pipeline.
*/
pipe->field = field;
break;
case V4L2_FIELD_INTERLACED_TB:
/* When interleaving fields only complete the buffer after
* capturing the second field.
*/
if (field == V4L2_FIELD_TOP)
return 1;
break;
case V4L2_FIELD_INTERLACED_BT:
if (field == V4L2_FIELD_BOTTOM)
return 1;
break;
}
buffer = omap3isp_video_buffer_next(&ccdc->video_out); buffer = omap3isp_video_buffer_next(&ccdc->video_out);
if (buffer != NULL) if (buffer != NULL)
ccdc_set_outaddr(ccdc, buffer->dma); ccdc_set_outaddr(ccdc, buffer->dma);
...@@ -1829,6 +1856,7 @@ ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh, ...@@ -1829,6 +1856,7 @@ ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh,
unsigned int width = fmt->width; unsigned int width = fmt->width;
unsigned int height = fmt->height; unsigned int height = fmt->height;
struct v4l2_rect *crop; struct v4l2_rect *crop;
enum v4l2_field field;
unsigned int i; unsigned int i;
switch (pad) { switch (pad) {
...@@ -1854,6 +1882,7 @@ ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh, ...@@ -1854,6 +1882,7 @@ ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh,
case CCDC_PAD_SOURCE_OF: case CCDC_PAD_SOURCE_OF:
pixelcode = fmt->code; pixelcode = fmt->code;
field = fmt->field;
*fmt = *__ccdc_get_format(ccdc, fh, CCDC_PAD_SINK, which); *fmt = *__ccdc_get_format(ccdc, fh, CCDC_PAD_SINK, which);
/* YUV formats are converted from 2X8 to 1X16 by the bridge and /* YUV formats are converted from 2X8 to 1X16 by the bridge and
...@@ -1878,6 +1907,17 @@ ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh, ...@@ -1878,6 +1907,17 @@ ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh,
crop = __ccdc_get_crop(ccdc, fh, which); crop = __ccdc_get_crop(ccdc, fh, which);
fmt->width = crop->width; fmt->width = crop->width;
fmt->height = crop->height; fmt->height = crop->height;
/* When input format is interlaced with alternating fields the
* CCDC can interleave the fields.
*/
if (fmt->field == V4L2_FIELD_ALTERNATE &&
(field == V4L2_FIELD_INTERLACED_TB ||
field == V4L2_FIELD_INTERLACED_BT)) {
fmt->field = field;
fmt->height *= 2;
}
break; break;
case CCDC_PAD_SOURCE_VP: case CCDC_PAD_SOURCE_VP:
......
...@@ -730,17 +730,13 @@ ...@@ -730,17 +730,13 @@
#define ISPCCDC_HSIZE_OFF_SHIFT 0 #define ISPCCDC_HSIZE_OFF_SHIFT 0
#define ISPCCDC_SDOFST_FINV (1 << 14) #define ISPCCDC_SDOFST_FIINV (1 << 14)
#define ISPCCDC_SDOFST_FOFST_1L 0 #define ISPCCDC_SDOFST_FOFST_SHIFT 12
#define ISPCCDC_SDOFST_FOFST_4L (3 << 12) #define ISPCCDC_SDOFST_FOFST_MASK (3 << 12)
#define ISPCCDC_SDOFST_LOFST3_SHIFT 0 #define ISPCCDC_SDOFST_LOFST3_SHIFT 0
#define ISPCCDC_SDOFST_LOFST2_SHIFT 3 #define ISPCCDC_SDOFST_LOFST2_SHIFT 3
#define ISPCCDC_SDOFST_LOFST1_SHIFT 6 #define ISPCCDC_SDOFST_LOFST1_SHIFT 6
#define ISPCCDC_SDOFST_LOFST0_SHIFT 9 #define ISPCCDC_SDOFST_LOFST0_SHIFT 9
#define EVENEVEN 1
#define ODDEVEN 2
#define EVENODD 3
#define ODDODD 4
#define ISPCCDC_CLAMP_OBGAIN_SHIFT 0 #define ISPCCDC_CLAMP_OBGAIN_SHIFT 0
#define ISPCCDC_CLAMP_OBST_SHIFT 10 #define ISPCCDC_CLAMP_OBST_SHIFT 10
......
...@@ -637,14 +637,40 @@ isp_video_set_format(struct file *file, void *fh, struct v4l2_format *format) ...@@ -637,14 +637,40 @@ isp_video_set_format(struct file *file, void *fh, struct v4l2_format *format)
if (format->type != video->type) if (format->type != video->type)
return -EINVAL; return -EINVAL;
/* Default to the progressive field order if the requested value is not /* Replace unsupported field orders with sane defaults. */
* supported (or set to ANY). The only supported orders are progressive switch (format->fmt.pix.field) {
* (available on all video nodes) and alternate (available on capture case V4L2_FIELD_NONE:
* nodes only). /* Progressive is supported everywhere. */
*/ break;
if (format->fmt.pix.field != V4L2_FIELD_ALTERNATE || case V4L2_FIELD_ALTERNATE:
video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) /* ALTERNATE is not supported on output nodes. */
if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
format->fmt.pix.field = V4L2_FIELD_NONE;
break;
case V4L2_FIELD_INTERLACED:
/* The ISP has no concept of video standard, select the
* top-bottom order when the unqualified interlaced order is
* requested.
*/
format->fmt.pix.field = V4L2_FIELD_INTERLACED_TB;
/* Fall-through */
case V4L2_FIELD_INTERLACED_TB:
case V4L2_FIELD_INTERLACED_BT:
/* Interlaced orders are only supported at the CCDC output. */
if (video != &video->isp->isp_ccdc.video_out)
format->fmt.pix.field = V4L2_FIELD_NONE;
break;
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
case V4L2_FIELD_SEQ_TB:
case V4L2_FIELD_SEQ_BT:
default:
/* All other field orders are currently unsupported, default to
* progressive.
*/
format->fmt.pix.field = V4L2_FIELD_NONE; format->fmt.pix.field = V4L2_FIELD_NONE;
break;
}
/* Fill the bytesperline and sizeimage fields by converting to media bus /* Fill the bytesperline and sizeimage fields by converting to media bus
* format and back to pixel format. * format and back to pixel format.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment