From d635711daa98be86d4c7fd01499c34f566b54ccb Mon Sep 17 00:00:00 2001 From: AndrĂ© Fabian Silva Delgado Date: Fri, 10 Jun 2016 05:30:17 -0300 Subject: Linux-libre 4.6.2-gnu --- drivers/staging/media/Kconfig | 8 +- drivers/staging/media/Makefile | 5 +- drivers/staging/media/bcm2048/radio-bcm2048.c | 7 +- .../staging/media/davinci_vpfe/davinci_vpfe_user.h | 4 +- drivers/staging/media/davinci_vpfe/dm365_ipipe.c | 11 +- .../staging/media/davinci_vpfe/dm365_ipipe_hw.c | 14 +- .../staging/media/davinci_vpfe/dm365_ipipe_hw.h | 7 +- drivers/staging/media/davinci_vpfe/dm365_ipipeif.c | 7 +- drivers/staging/media/davinci_vpfe/dm365_isif.c | 24 +- drivers/staging/media/davinci_vpfe/dm365_resizer.c | 20 +- .../staging/media/davinci_vpfe/vpfe_mc_capture.c | 6 +- drivers/staging/media/davinci_vpfe/vpfe_video.c | 46 +- drivers/staging/media/lirc/lirc_parallel.c | 24 +- drivers/staging/media/lirc/lirc_zilog.c | 4 +- drivers/staging/media/mn88472/mn88472.c | 12 +- drivers/staging/media/mn88473/Kconfig | 7 - drivers/staging/media/mn88473/Makefile | 5 - drivers/staging/media/mn88473/TODO | 21 - drivers/staging/media/mn88473/mn88473.c | 522 ------ drivers/staging/media/mn88473/mn88473_priv.h | 37 - drivers/staging/media/mx2/Kconfig | 15 + drivers/staging/media/mx2/Makefile | 3 + drivers/staging/media/mx2/TODO | 10 + drivers/staging/media/mx2/mx2_camera.c | 1636 ++++++++++++++++++ drivers/staging/media/mx3/Kconfig | 15 + drivers/staging/media/mx3/Makefile | 3 + drivers/staging/media/mx3/TODO | 10 + drivers/staging/media/mx3/mx3_camera.c | 1264 ++++++++++++++ drivers/staging/media/omap1/Kconfig | 13 + drivers/staging/media/omap1/Makefile | 3 + drivers/staging/media/omap1/TODO | 8 + drivers/staging/media/omap1/omap1_camera.c | 1738 ++++++++++++++++++++ drivers/staging/media/omap4iss/iss.c | 213 +-- drivers/staging/media/omap4iss/iss.h | 6 +- drivers/staging/media/omap4iss/iss_video.c | 15 +- drivers/staging/media/omap4iss/iss_video.h | 1 - drivers/staging/media/timb/Kconfig | 11 + drivers/staging/media/timb/Makefile | 1 + drivers/staging/media/timb/timblogiw.c | 870 ++++++++++ 39 files changed, 5712 insertions(+), 914 deletions(-) delete mode 100644 drivers/staging/media/mn88473/Kconfig delete mode 100644 drivers/staging/media/mn88473/Makefile delete mode 100644 drivers/staging/media/mn88473/TODO delete mode 100644 drivers/staging/media/mn88473/mn88473.c delete mode 100644 drivers/staging/media/mn88473/mn88473_priv.h create mode 100644 drivers/staging/media/mx2/Kconfig create mode 100644 drivers/staging/media/mx2/Makefile create mode 100644 drivers/staging/media/mx2/TODO create mode 100644 drivers/staging/media/mx2/mx2_camera.c create mode 100644 drivers/staging/media/mx3/Kconfig create mode 100644 drivers/staging/media/mx3/Makefile create mode 100644 drivers/staging/media/mx3/TODO create mode 100644 drivers/staging/media/mx3/mx3_camera.c create mode 100644 drivers/staging/media/omap1/Kconfig create mode 100644 drivers/staging/media/omap1/Makefile create mode 100644 drivers/staging/media/omap1/TODO create mode 100644 drivers/staging/media/omap1/omap1_camera.c create mode 100644 drivers/staging/media/timb/Kconfig create mode 100644 drivers/staging/media/timb/Makefile create mode 100644 drivers/staging/media/timb/timblogiw.c (limited to 'drivers/staging/media') diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 14697686e..0078b6a92 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -27,10 +27,16 @@ source "drivers/staging/media/davinci_vpfe/Kconfig" source "drivers/staging/media/mn88472/Kconfig" -source "drivers/staging/media/mn88473/Kconfig" +source "drivers/staging/media/mx2/Kconfig" + +source "drivers/staging/media/mx3/Kconfig" + +source "drivers/staging/media/omap1/Kconfig" source "drivers/staging/media/omap4iss/Kconfig" +source "drivers/staging/media/timb/Kconfig" + # Keep LIRC at the end, as it has sub-menus source "drivers/staging/media/lirc/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 34c557b4c..91495882a 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -2,6 +2,9 @@ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ +obj-$(CONFIG_VIDEO_MX2) += mx2/ +obj-$(CONFIG_VIDEO_MX3) += mx3/ +obj-$(CONFIG_VIDEO_OMAP1) += omap1/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_DVB_MN88472) += mn88472/ -obj-$(CONFIG_DVB_MN88473) += mn88473/ +obj-$(CONFIG_VIDEO_TIMBERDALE) += timb/ diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.c b/drivers/staging/media/bcm2048/radio-bcm2048.c index 8fdf0ac4f..abf330f92 100644 --- a/drivers/staging/media/bcm2048/radio-bcm2048.c +++ b/drivers/staging/media/bcm2048/radio-bcm2048.c @@ -1828,17 +1828,14 @@ static int bcm2048_deinit(struct bcm2048_device *bdev) err = bcm2048_set_audio_route(bdev, 0); if (err < 0) - goto exit; + return err; err = bcm2048_set_dac_output(bdev, 0); if (err < 0) - goto exit; + return err; err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); - if (err < 0) - goto exit; -exit: return err; } diff --git a/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h index 7b7e7b26c..f4f35c9ad 100644 --- a/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h +++ b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h @@ -538,8 +538,8 @@ struct vpfe_isif_raw_config { }; /********************************************************************** - IPIPE API Structures -**********************************************************************/ + * IPIPE API Structures + **********************************************************************/ /* IPIPE module configurations */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c index ac78ed2f8..ff47a8f36 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_ipipe.c +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c @@ -1350,21 +1350,16 @@ error: */ static long ipipe_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { - int ret = 0; - switch (cmd) { case VIDIOC_VPFE_IPIPE_S_CONFIG: - ret = ipipe_s_config(sd, arg); - break; + return ipipe_s_config(sd, arg); case VIDIOC_VPFE_IPIPE_G_CONFIG: - ret = ipipe_g_config(sd, arg); - break; + return ipipe_g_config(sd, arg); default: - ret = -ENOIOCTLCMD; + return -ENOIOCTLCMD; } - return ret; } void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en) diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c index b1d5e23ae..958ef71ee 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c @@ -242,7 +242,7 @@ static int get_ipipe_mode(struct vpfe_ipipe_device *ipipe) if (ipipeif_sink == IPIPEIF_INPUT_MEMORY) return IPIPE_MODE_SINGLE_SHOT; - else if (ipipeif_sink == IPIPEIF_INPUT_ISIF) + if (ipipeif_sink == IPIPEIF_INPUT_ISIF) return IPIPE_MODE_CONTINUOUS; return -EINVAL; @@ -682,8 +682,10 @@ ipipe_set_rgb2rgb_regs(void __iomem *base_addr, unsigned int id, ipipe_clock_enable(base_addr); if (id == IPIPE_RGB2RGB_2) { - /* For second RGB module, gain integer is 3 bits instead - of 4, offset has 11 bits insread of 13 */ + /* + * For second RGB module, gain integer is 3 bits instead + * of 4, offset has 11 bits insread of 13 + */ offset = RGB2_MUL_BASE; integ_mask = 0x7; offset_mask = RGB2RGB_2_OFST_MASK; @@ -792,8 +794,10 @@ ipipe_set_3d_lut_regs(void __iomem *base_addr, void __iomem *isp5_base_addr, /* valied table */ tbl = lut_3d->table; for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++) { - /* Each entry has 0-9 (B), 10-19 (G) and - 20-29 R values */ + /* + * Each entry has 0-9 (B), 10-19 (G) and + * 20-29 R values + */ val = tbl[i].b & D3_LUT_ENTRY_MASK; val |= (tbl[i].g & D3_LUT_ENTRY_MASK) << D3_LUT_ENTRY_G_SHIFT; diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h index 2bf2f7a69..7ee157233 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h @@ -278,9 +278,10 @@ /* Resizer Rescale Parameters */ #define RSZ_EN_A 0x58 #define RSZ_EN_B 0xe8 -/* offset of the registers to be added with base register of - either RSZ0 or RSZ1 -*/ +/* + * offset of the registers to be added with base register of + * either RSZ0 or RSZ1 + */ #define RSZ_MODE 0x4 #define RSZ_420 0x8 #define RSZ_I_VPS 0xc diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c index 633d6456f..46fd2c7f6 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c @@ -94,7 +94,7 @@ static int get_oneshot_mode(enum ipipeif_input_entity input) { if (input == IPIPEIF_INPUT_MEMORY) return IPIPEIF_MODE_ONE_SHOT; - else if (input == IPIPEIF_INPUT_ISIF) + if (input == IPIPEIF_INPUT_ISIF) return IPIPEIF_MODE_CONTINUOUS; return -EINVAL; @@ -641,8 +641,9 @@ ipipeif_try_format(struct vpfe_ipipeif_device *ipipeif, } static int -ipipeif_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_frame_size_enum *fse) +ipipeif_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) { struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt format; diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.c b/drivers/staging/media/davinci_vpfe/dm365_isif.c index 99057892d..ae9202ded 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_isif.c +++ b/drivers/staging/media/davinci_vpfe/dm365_isif.c @@ -282,7 +282,8 @@ isif_config_format(struct vpfe_device *vpfe_dev, unsigned int pad) * @fmt: pointer to v4l2 subdev format structure */ static void -isif_try_format(struct vpfe_isif_device *isif, struct v4l2_subdev_pad_config *cfg, +isif_try_format(struct vpfe_isif_device *isif, + struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { unsigned int width = fmt->format.width; @@ -625,21 +626,16 @@ static int isif_set_params(struct v4l2_subdev *sd, void *params) */ static long isif_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { - int ret; - switch (cmd) { case VIDIOC_VPFE_ISIF_S_RAW_PARAMS: - ret = isif_set_params(sd, arg); - break; + return isif_set_params(sd, arg); case VIDIOC_VPFE_ISIF_G_RAW_PARAMS: - ret = isif_get_params(sd, arg); - break; + return isif_get_params(sd, arg); default: - ret = -ENOIOCTLCMD; + return -ENOIOCTLCMD; } - return ret; } static void isif_config_gain_offset(struct vpfe_isif_device *isif) @@ -1239,7 +1235,8 @@ static int isif_config_ycbcr(struct v4l2_subdev *sd, int mode) * a lot of registers that we didn't touch */ /* start with all bits zero */ - ccdcfg = modeset = 0; + ccdcfg = 0; + modeset = 0; pix_fmt = isif_get_pix_fmt(format->code); if (pix_fmt < 0) { pr_debug("Invalid pix_fmt(input mode)\n"); @@ -1398,8 +1395,9 @@ static int isif_set_stream(struct v4l2_subdev *sd, int enable) * @which: wanted subdev format. */ static struct v4l2_mbus_framefmt * -__isif_get_format(struct vpfe_isif_device *isif, struct v4l2_subdev_pad_config *cfg, - unsigned int pad, enum v4l2_subdev_format_whence which) +__isif_get_format(struct vpfe_isif_device *isif, + struct v4l2_subdev_pad_config *cfg, unsigned int pad, + enum v4l2_subdev_format_whence which) { if (which == V4L2_SUBDEV_FORMAT_TRY) { struct v4l2_subdev_format fmt; @@ -1570,7 +1568,7 @@ isif_pad_set_selection(struct v4l2_subdev *sd, sel->r.height = format->height; } /* adjust the width to 16 pixel boundary */ - sel->r.width = ((sel->r.width + 15) & ~0xf); + sel->r.width = (sel->r.width + 15) & ~0xf; vpfe_isif->crop = sel->r; if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { isif_set_image_window(vpfe_isif); diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.c b/drivers/staging/media/davinci_vpfe/dm365_resizer.c index a91395ce9..3cd56cc13 100644 --- a/drivers/staging/media/davinci_vpfe/dm365_resizer.c +++ b/drivers/staging/media/davinci_vpfe/dm365_resizer.c @@ -404,7 +404,7 @@ resizer_calculate_down_scale_f_div_param(struct device *dev, param->f_div.pass[0].src_hsz = upper_h1 + o; param->f_div.pass[1].o_hsz = h2 - 1; param->f_div.pass[1].i_hps = 10 + (val1 * two_power); - param->f_div.pass[1].h_phs = (val - (val1 << 8)); + param->f_div.pass[1].h_phs = val - (val1 << 8); param->f_div.pass[1].src_hps = upper_h1 - o; param->f_div.pass[1].src_hsz = upper_h2 + o; @@ -425,8 +425,8 @@ resizer_configure_common_in_params(struct vpfe_resizer_device *resizer) param->rsz_common.hps = param->user_config.hst; if (vpfe_ipipeif_decimation_enabled(vpfe_dev)) - param->rsz_common.hsz = (((informat->width - 1) * - IPIPEIF_RSZ_CONST) / vpfe_ipipeif_get_rsz(vpfe_dev)); + param->rsz_common.hsz = ((informat->width - 1) * + IPIPEIF_RSZ_CONST) / vpfe_ipipeif_get_rsz(vpfe_dev); else param->rsz_common.hsz = informat->width - 1; @@ -650,7 +650,7 @@ resizer_calculate_normal_f_div_param(struct device *dev, int input_width, param->f_div.pass[0].src_hsz = (input_width >> 2) + o; param->f_div.pass[1].o_hsz = h2 - 1; param->f_div.pass[1].i_hps = val1; - param->f_div.pass[1].h_phs = (val - (val1 << 8)); + param->f_div.pass[1].h_phs = val - (val1 << 8); param->f_div.pass[1].src_hps = (input_width >> 2) - o; param->f_div.pass[1].src_hsz = (input_width >> 2) + o; @@ -1387,8 +1387,9 @@ resizer_try_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, * @fmt: pointer to v4l2 subdev format structure * return -EINVAL or zero on success */ -static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) +static int resizer_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) { struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *format; @@ -1447,8 +1448,9 @@ static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_con * @fmt: pointer to v4l2 subdev format structure * return -EINVAL or zero on success */ -static int resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) +static int resizer_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) { struct v4l2_mbus_framefmt *format; @@ -1670,7 +1672,7 @@ static int resizer_link_setup(struct media_entity *entity, resizer->crop_resizer.input = RESIZER_CROP_INPUT_IPIPEIF; else if (ipipe_source == IPIPE_OUTPUT_RESIZER) - resizer->crop_resizer.input = + resizer->crop_resizer.input = RESIZER_CROP_INPUT_IPIPE; else return -EINVAL; diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c index ec46f366d..bf077f834 100644 --- a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c +++ b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c @@ -442,8 +442,10 @@ static int vpfe_register_entities(struct vpfe_device *vpfe_dev) /* create links now, starting with external(i2c) entities */ for (i = 0; i < vpfe_dev->num_ext_subdevs; i++) - /* if entity has no pads (ex: amplifier), - cant establish link */ + /* + * if entity has no pads (ex: amplifier), + * cant establish link + */ if (vpfe_dev->sd[i]->entity.num_pads) { ret = media_create_pad_link(&vpfe_dev->sd[i]->entity, 0, &vpfe_dev->vpfe_isif.subdev.entity, diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.c b/drivers/staging/media/davinci_vpfe/vpfe_video.c index db49af902..be72a8e5f 100644 --- a/drivers/staging/media/davinci_vpfe/vpfe_video.c +++ b/drivers/staging/media/davinci_vpfe/vpfe_video.c @@ -178,9 +178,10 @@ static int vpfe_update_pipe_state(struct vpfe_video_device *video) if (ret) return ret; - /* Find out if there is any input video - if yes, it is single shot. - */ + /* + * Find out if there is any input video + * if yes, it is single shot. + */ if (pipe->input_num == 0) { pipe->state = VPFE_PIPELINE_STREAM_CONTINUOUS; ret = vpfe_update_current_ext_subdev(video); @@ -460,7 +461,7 @@ void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video) video->next_frm = list_entry(video->dma_queue.next, struct vpfe_cap_buffer, list); - if (VPFE_PIPELINE_STREAM_SINGLESHOT == video->pipe.state) + if (video->pipe.state == VPFE_PIPELINE_STREAM_SINGLESHOT) video->cur_frm = video->next_frm; list_del(&video->next_frm->list); @@ -529,10 +530,11 @@ static int vpfe_release(struct file *file) if (fh->io_allowed) { if (video->started) { vpfe_stop_capture(video); - /* mark pipe state as stopped in vpfe_release(), - as app might call streamon() after streamoff() - in which case driver has to start streaming. - */ + /* + * mark pipe state as stopped in vpfe_release(), + * as app might call streamon() after streamoff() + * in which case driver has to start streaming. + */ video->pipe.state = VPFE_PIPELINE_STREAM_STOPPED; vb2_streamoff(&video->buffer_queue, video->buffer_queue.type); @@ -672,8 +674,10 @@ static int vpfe_enum_fmt(struct file *file, void *priv, v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_fmt\n"); - /* since already subdev pad format is set, - only one pixel format is available */ + /* + * since already subdev pad format is set, + * only one pixel format is available + */ if (fmt->index > 0) { v4l2_err(&vpfe_dev->v4l2_dev, "Invalid index\n"); return -EINVAL; @@ -1328,8 +1332,8 @@ static int vpfe_reqbufs(struct file *file, void *priv, v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_reqbufs\n"); - if (V4L2_BUF_TYPE_VIDEO_CAPTURE != req_buf->type && - V4L2_BUF_TYPE_VIDEO_OUTPUT != req_buf->type) { + if (req_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + req_buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT){ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buffer type\n"); return -EINVAL; } @@ -1390,8 +1394,8 @@ static int vpfe_querybuf(struct file *file, void *priv, v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querybuf\n"); - if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type && - V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); return -EINVAL; } @@ -1417,8 +1421,8 @@ static int vpfe_qbuf(struct file *file, void *priv, v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_qbuf\n"); - if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type && - V4L2_BUF_TYPE_VIDEO_OUTPUT != p->type) { + if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + p->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); return -EINVAL; } @@ -1445,8 +1449,8 @@ static int vpfe_dqbuf(struct file *file, void *priv, v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_dqbuf\n"); - if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type && - V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); return -EINVAL; } @@ -1478,8 +1482,8 @@ static int vpfe_streamon(struct file *file, void *priv, v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamon\n"); - if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type && - V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) { + if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + buf_type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); return ret; } @@ -1495,7 +1499,7 @@ static int vpfe_streamon(struct file *file, void *priv, return -EIO; } /* Validate the pipeline */ - if (V4L2_BUF_TYPE_VIDEO_CAPTURE == buf_type) { + if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { ret = vpfe_video_validate_pipeline(pipe); if (ret < 0) return ret; diff --git a/drivers/staging/media/lirc/lirc_parallel.c b/drivers/staging/media/lirc/lirc_parallel.c index d009bcb43..68ede6c56 100644 --- a/drivers/staging/media/lirc/lirc_parallel.c +++ b/drivers/staging/media/lirc/lirc_parallel.c @@ -193,7 +193,7 @@ static int lirc_claim(void) return 0; } } - out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP); + out(LIRC_LP_CONTROL, LP_PSELECP | LP_PINITP); is_claimed = 1; return 1; } @@ -264,7 +264,7 @@ static void lirc_lirc_irq_handler(void *blah) init = 1; } - timeout = timer/10; /* timeout after 1/10 sec. */ + timeout = timer / 10; /* timeout after 1/10 sec. */ signal = 1; level = lirc_get_timer(); do { @@ -286,15 +286,15 @@ static void lirc_lirc_irq_handler(void *blah) /* adjust value to usecs */ __u64 helper; - helper = ((__u64) signal)*1000000; + helper = ((__u64)signal) * 1000000; do_div(helper, timer); - signal = (long) helper; + signal = (long)helper; if (signal > LIRC_SFH506_DELAY) data = signal - LIRC_SFH506_DELAY; else data = 1; - rbuf_write(PULSE_BIT|data); /* pulse */ + rbuf_write(PULSE_BIT | data); /* pulse */ } lastkt = ktime_get(); #else @@ -331,7 +331,7 @@ static ssize_t lirc_read(struct file *filep, char __user *buf, size_t n, set_current_state(TASK_INTERRUPTIBLE); while (count < n) { if (rptr != wptr) { - if (copy_to_user(buf+count, &rbuf[rptr], + if (copy_to_user(buf + count, &rbuf[rptr], sizeof(int))) { result = -EFAULT; break; @@ -393,9 +393,9 @@ static ssize_t lirc_write(struct file *filep, const char __user *buf, size_t n, for (i = 0; i < count; i++) { __u64 helper; - helper = ((__u64) wbuf[i])*timer; + helper = ((__u64)wbuf[i]) * timer; do_div(helper, 1000000); - wbuf[i] = (int) helper; + wbuf[i] = (int)helper; } local_irq_save(flags); @@ -647,7 +647,7 @@ static int __init lirc_parallel_init(void) goto exit_device_put; pport = parport_find_base(io); - if (pport == NULL) { + if (!pport) { pr_notice("no port at %x found\n", io); result = -ENXIO; goto exit_device_put; @@ -656,7 +656,7 @@ static int __init lirc_parallel_init(void) pf, kf, lirc_lirc_irq_handler, 0, NULL); parport_put_port(pport); - if (ppdevice == NULL) { + if (!ppdevice) { pr_notice("parport_register_device() failed\n"); result = -ENXIO; goto exit_device_put; @@ -664,7 +664,7 @@ static int __init lirc_parallel_init(void) if (parport_claim(ppdevice) != 0) goto skip_init; is_claimed = 1; - out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP); + out(LIRC_LP_CONTROL, LP_PSELECP | LP_PINITP); #ifdef LIRC_TIMER if (debug) @@ -730,7 +730,7 @@ module_param(irq, int, S_IRUGO); MODULE_PARM_DESC(irq, "Interrupt (7 or 5)"); module_param(tx_mask, int, S_IRUGO); -MODULE_PARM_DESC(tx_maxk, "Transmitter mask (default: 0x01)"); +MODULE_PARM_DESC(tx_mask, "Transmitter mask (default: 0x01)"); module_param(debug, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/staging/media/lirc/lirc_zilog.c b/drivers/staging/media/lirc/lirc_zilog.c index e0d72062f..b03c089b7 100644 --- a/drivers/staging/media/lirc/lirc_zilog.c +++ b/drivers/staging/media/lirc/lirc_zilog.c @@ -1680,9 +1680,7 @@ module_init(zilog_init); module_exit(zilog_exit); MODULE_DESCRIPTION("Zilog/Hauppauge infrared transmitter driver (i2c stack)"); -MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, " - "Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver, " - "Andy Walls"); +MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver, Andy Walls"); MODULE_LICENSE("GPL"); /* for compat with old name, which isn't all that accurate anymore */ MODULE_ALIAS("lirc_pvr150"); diff --git a/drivers/staging/media/mn88472/mn88472.c b/drivers/staging/media/mn88472/mn88472.c index 0295f5639..c7a76f0fc 100644 --- a/drivers/staging/media/mn88472/mn88472.c +++ b/drivers/staging/media/mn88472/mn88472.c @@ -96,9 +96,9 @@ static int mn88472_set_frontend(struct dvb_frontend *fe) /* Calculate IF registers ( (1<<24)*IF / Xtal ) */ tmp = div_u64(if_frequency * (u64)(1<<24) + (dev->xtal / 2), dev->xtal); - if_val[0] = ((tmp >> 16) & 0xff); - if_val[1] = ((tmp >> 8) & 0xff); - if_val[2] = ((tmp >> 0) & 0xff); + if_val[0] = (tmp >> 16) & 0xff; + if_val[1] = (tmp >> 8) & 0xff; + if_val[2] = (tmp >> 0) & 0xff; ret = regmap_write(dev->regmap[2], 0xfb, 0x13); ret = regmap_write(dev->regmap[2], 0xef, 0x13); @@ -456,7 +456,7 @@ static int mn88472_probe(struct i2c_client *client, } dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (dev == NULL) { + if (!dev) { ret = -ENOMEM; goto err; } @@ -483,7 +483,7 @@ static int mn88472_probe(struct i2c_client *client, * 0x1a and 0x1c, in order to get own I2C client for each register page. */ dev->client[1] = i2c_new_dummy(client->adapter, 0x1a); - if (dev->client[1] == NULL) { + if (!dev->client[1]) { ret = -ENODEV; dev_err(&client->dev, "I2C registration failed\n"); if (ret) @@ -497,7 +497,7 @@ static int mn88472_probe(struct i2c_client *client, i2c_set_clientdata(dev->client[1], dev); dev->client[2] = i2c_new_dummy(client->adapter, 0x1c); - if (dev->client[2] == NULL) { + if (!dev->client[2]) { ret = -ENODEV; dev_err(&client->dev, "2nd I2C registration failed\n"); if (ret) diff --git a/drivers/staging/media/mn88473/Kconfig b/drivers/staging/media/mn88473/Kconfig deleted file mode 100644 index 6c9ebf51c..000000000 --- a/drivers/staging/media/mn88473/Kconfig +++ /dev/null @@ -1,7 +0,0 @@ -config DVB_MN88473 - tristate "Panasonic MN88473" - depends on DVB_CORE && I2C - select REGMAP_I2C - default m if !MEDIA_SUBDRV_AUTOSELECT - help - Say Y when you want to support this frontend. diff --git a/drivers/staging/media/mn88473/Makefile b/drivers/staging/media/mn88473/Makefile deleted file mode 100644 index fac55410c..000000000 --- a/drivers/staging/media/mn88473/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -obj-$(CONFIG_DVB_MN88473) += mn88473.o - -ccflags-y += -Idrivers/media/dvb-core/ -ccflags-y += -Idrivers/media/dvb-frontends/ -ccflags-y += -Idrivers/media/tuners/ diff --git a/drivers/staging/media/mn88473/TODO b/drivers/staging/media/mn88473/TODO deleted file mode 100644 index b90a14be3..000000000 --- a/drivers/staging/media/mn88473/TODO +++ /dev/null @@ -1,21 +0,0 @@ -Driver general quality is not good enough for mainline. Also, other -device drivers (USB-bridge, tuner) needed for Astrometa receiver in -question could need some changes. However, if that driver is mainlined -due to some other device than Astrometa, unrelated TODOs could be -skipped. In that case rtl28xxu driver needs module parameter to prevent -driver loading. - -Required TODOs: -* missing lock flags -* I2C errors -* tuner sensitivity - -*Do not* send any patch fixing checkpatch.pl issues. Currently it passes -checkpatch.pl tests. I don't want waste my time to review this kind of -trivial stuff. *Do not* add missing register I/O error checks. Those are -missing for the reason it is much easier to compare I2C data sniffs when -there is less lines. Those error checks are about the last thing to be added. - -Patches should be submitted to: -linux-media@vger.kernel.org and Antti Palosaari - diff --git a/drivers/staging/media/mn88473/mn88473.c b/drivers/staging/media/mn88473/mn88473.c deleted file mode 100644 index 18439db24..000000000 --- a/drivers/staging/media/mn88473/mn88473.c +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Panasonic MN88473 DVB-T/T2/C demodulator driver - * - * Copyright (C) 2014 Antti Palosaari - * - * 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 "mn88473_priv.h" - -static int mn88473_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) -{ - s->min_delay_ms = 1000; - return 0; -} - -static int mn88473_set_frontend(struct dvb_frontend *fe) -{ - struct i2c_client *client = fe->demodulator_priv; - struct mn88473_dev *dev = i2c_get_clientdata(client); - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret, i; - u32 if_frequency; - u64 tmp; - u8 delivery_system_val, if_val[3], bw_val[7]; - - dev_dbg(&client->dev, - "delivery_system=%u modulation=%u frequency=%u bandwidth_hz=%u symbol_rate=%u inversion=%d stream_id=%d\n", - c->delivery_system, - c->modulation, - c->frequency, - c->bandwidth_hz, - c->symbol_rate, - c->inversion, - c->stream_id); - - if (!dev->warm) { - ret = -EAGAIN; - goto err; - } - - switch (c->delivery_system) { - case SYS_DVBT: - delivery_system_val = 0x02; - break; - case SYS_DVBT2: - delivery_system_val = 0x03; - break; - case SYS_DVBC_ANNEX_A: - delivery_system_val = 0x04; - break; - default: - ret = -EINVAL; - goto err; - } - - if (c->bandwidth_hz <= 6000000) { - memcpy(bw_val, "\xe9\x55\x55\x1c\x29\x1c\x29", 7); - } else if (c->bandwidth_hz <= 7000000) { - memcpy(bw_val, "\xc8\x00\x00\x17\x0a\x17\x0a", 7); - } else if (c->bandwidth_hz <= 8000000) { - memcpy(bw_val, "\xaf\x00\x00\x11\xec\x11\xec", 7); - } else { - ret = -EINVAL; - goto err; - } - - /* program tuner */ - if (fe->ops.tuner_ops.set_params) { - ret = fe->ops.tuner_ops.set_params(fe); - if (ret) - goto err; - } - - if (fe->ops.tuner_ops.get_if_frequency) { - ret = fe->ops.tuner_ops.get_if_frequency(fe, &if_frequency); - if (ret) - goto err; - - dev_dbg(&client->dev, "get_if_frequency=%d\n", if_frequency); - } else { - if_frequency = 0; - } - - /* Calculate IF registers ( (1<<24)*IF / Xtal ) */ - tmp = div_u64(if_frequency * (u64)(1<<24) + (dev->xtal / 2), - dev->xtal); - if_val[0] = ((tmp >> 16) & 0xff); - if_val[1] = ((tmp >> 8) & 0xff); - if_val[2] = ((tmp >> 0) & 0xff); - - ret = regmap_write(dev->regmap[2], 0x05, 0x00); - ret = regmap_write(dev->regmap[2], 0xfb, 0x13); - ret = regmap_write(dev->regmap[2], 0xef, 0x13); - ret = regmap_write(dev->regmap[2], 0xf9, 0x13); - ret = regmap_write(dev->regmap[2], 0x00, 0x18); - ret = regmap_write(dev->regmap[2], 0x01, 0x01); - ret = regmap_write(dev->regmap[2], 0x02, 0x21); - ret = regmap_write(dev->regmap[2], 0x03, delivery_system_val); - ret = regmap_write(dev->regmap[2], 0x0b, 0x00); - - for (i = 0; i < sizeof(if_val); i++) { - ret = regmap_write(dev->regmap[2], 0x10 + i, if_val[i]); - if (ret) - goto err; - } - - for (i = 0; i < sizeof(bw_val); i++) { - ret = regmap_write(dev->regmap[2], 0x13 + i, bw_val[i]); - if (ret) - goto err; - } - - ret = regmap_write(dev->regmap[2], 0x2d, 0x3b); - ret = regmap_write(dev->regmap[2], 0x2e, 0x00); - ret = regmap_write(dev->regmap[2], 0x56, 0x0d); - ret = regmap_write(dev->regmap[0], 0x01, 0xba); - ret = regmap_write(dev->regmap[0], 0x02, 0x13); - ret = regmap_write(dev->regmap[0], 0x03, 0x80); - ret = regmap_write(dev->regmap[0], 0x04, 0xba); - ret = regmap_write(dev->regmap[0], 0x05, 0x91); - ret = regmap_write(dev->regmap[0], 0x07, 0xe7); - ret = regmap_write(dev->regmap[0], 0x08, 0x28); - ret = regmap_write(dev->regmap[0], 0x0a, 0x1a); - ret = regmap_write(dev->regmap[0], 0x13, 0x1f); - ret = regmap_write(dev->regmap[0], 0x19, 0x03); - ret = regmap_write(dev->regmap[0], 0x1d, 0xb0); - ret = regmap_write(dev->regmap[0], 0x2a, 0x72); - ret = regmap_write(dev->regmap[0], 0x2d, 0x00); - ret = regmap_write(dev->regmap[0], 0x3c, 0x00); - ret = regmap_write(dev->regmap[0], 0x3f, 0xf8); - ret = regmap_write(dev->regmap[0], 0x40, 0xf4); - ret = regmap_write(dev->regmap[0], 0x41, 0x08); - ret = regmap_write(dev->regmap[0], 0xd2, 0x29); - ret = regmap_write(dev->regmap[0], 0xd4, 0x55); - ret = regmap_write(dev->regmap[1], 0x10, 0x10); - ret = regmap_write(dev->regmap[1], 0x11, 0xab); - ret = regmap_write(dev->regmap[1], 0x12, 0x0d); - ret = regmap_write(dev->regmap[1], 0x13, 0xae); - ret = regmap_write(dev->regmap[1], 0x14, 0x1d); - ret = regmap_write(dev->regmap[1], 0x15, 0x9d); - ret = regmap_write(dev->regmap[1], 0xbe, 0x08); - ret = regmap_write(dev->regmap[2], 0x09, 0x08); - ret = regmap_write(dev->regmap[2], 0x08, 0x1d); - ret = regmap_write(dev->regmap[0], 0xb2, 0x37); - ret = regmap_write(dev->regmap[0], 0xd7, 0x04); - ret = regmap_write(dev->regmap[2], 0x32, 0x80); - ret = regmap_write(dev->regmap[2], 0x36, 0x00); - ret = regmap_write(dev->regmap[2], 0xf8, 0x9f); - if (ret) - goto err; - - dev->delivery_system = c->delivery_system; - - return 0; -err: - dev_dbg(&client->dev, "failed=%d\n", ret); - return ret; -} - -static int mn88473_read_status(struct dvb_frontend *fe, enum fe_status *status) -{ - struct i2c_client *client = fe->demodulator_priv; - struct mn88473_dev *dev = i2c_get_clientdata(client); - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - unsigned int utmp; - int lock = 0; - - *status = 0; - - if (!dev->warm) { - ret = -EAGAIN; - goto err; - } - - switch (c->delivery_system) { - case SYS_DVBT: - ret = regmap_read(dev->regmap[0], 0x62, &utmp); - if (ret) - goto err; - if (!(utmp & 0xA0)) { - if ((utmp & 0xF) >= 0x03) - *status |= FE_HAS_SIGNAL; - if ((utmp & 0xF) >= 0x09) - lock = 1; - } - break; - case SYS_DVBT2: - ret = regmap_read(dev->regmap[2], 0x8B, &utmp); - if (ret) - goto err; - if (!(utmp & 0x40)) { - if ((utmp & 0xF) >= 0x07) - *status |= FE_HAS_SIGNAL; - if ((utmp & 0xF) >= 0x0a) - *status |= FE_HAS_CARRIER; - if ((utmp & 0xF) >= 0x0d) - *status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - } - break; - case SYS_DVBC_ANNEX_A: - ret = regmap_read(dev->regmap[1], 0x85, &utmp); - if (ret) - goto err; - if (!(utmp & 0x40)) { - ret = regmap_read(dev->regmap[1], 0x89, &utmp); - if (ret) - goto err; - if (utmp & 0x01) - lock = 1; - } - break; - default: - ret = -EINVAL; - goto err; - } - - if (lock) - *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | - FE_HAS_SYNC | FE_HAS_LOCK; - - return 0; -err: - dev_dbg(&client->dev, "failed=%d\n", ret); - return ret; -} - -static int mn88473_init(struct dvb_frontend *fe) -{ - struct i2c_client *client = fe->demodulator_priv; - struct mn88473_dev *dev = i2c_get_clientdata(client); - int ret, len, remaining; - const struct firmware *fw = NULL; - u8 *fw_file = MN88473_FIRMWARE; - unsigned int tmp; - - dev_dbg(&client->dev, "\n"); - - /* set cold state by default */ - dev->warm = false; - - /* check if firmware is already running */ - ret = regmap_read(dev->regmap[0], 0xf5, &tmp); - if (ret) - goto err; - - if (!(tmp & 0x1)) { - dev_info(&client->dev, "firmware already running\n"); - dev->warm = true; - return 0; - } - - /* request the firmware, this will block and timeout */ - ret = reject_firmware(&fw, fw_file, &client->dev); - if (ret) { - dev_err(&client->dev, "firmare file '%s' not found\n", fw_file); - goto err_request_firmware; - } - - dev_info(&client->dev, "downloading firmware from file '%s'\n", - fw_file); - - ret = regmap_write(dev->regmap[0], 0xf5, 0x03); - if (ret) - goto err; - - for (remaining = fw->size; remaining > 0; - remaining -= (dev->i2c_wr_max - 1)) { - len = remaining; - if (len > (dev->i2c_wr_max - 1)) - len = dev->i2c_wr_max - 1; - - ret = regmap_bulk_write(dev->regmap[0], 0xf6, - &fw->data[fw->size - remaining], len); - if (ret) { - dev_err(&client->dev, "firmware download failed=%d\n", - ret); - goto err; - } - } - - /* parity check of firmware */ - ret = regmap_read(dev->regmap[0], 0xf8, &tmp); - if (ret) { - dev_err(&client->dev, - "parity reg read failed=%d\n", ret); - goto err; - } - if (tmp & 0x10) { - dev_err(&client->dev, - "firmware parity check failed=0x%x\n", tmp); - goto err; - } - dev_err(&client->dev, "firmware parity check succeeded=0x%x\n", tmp); - - ret = regmap_write(dev->regmap[0], 0xf5, 0x00); - if (ret) - goto err; - - release_firmware(fw); - fw = NULL; - - /* warm state */ - dev->warm = true; - - return 0; - -err: - release_firmware(fw); -err_request_firmware: - dev_dbg(&client->dev, "failed=%d\n", ret); - return ret; -} - -static int mn88473_sleep(struct dvb_frontend *fe) -{ - struct i2c_client *client = fe->demodulator_priv; - struct mn88473_dev *dev = i2c_get_clientdata(client); - int ret; - - dev_dbg(&client->dev, "\n"); - - ret = regmap_write(dev->regmap[2], 0x05, 0x3e); - if (ret) - goto err; - - dev->delivery_system = SYS_UNDEFINED; - - return 0; -err: - dev_dbg(&client->dev, "failed=%d\n", ret); - return ret; -} - -static struct dvb_frontend_ops mn88473_ops = { - .delsys = {SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_AC}, - .info = { - .name = "Panasonic MN88473", - .symbol_rate_min = 1000000, - .symbol_rate_max = 7200000, - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QPSK | - FE_CAN_QAM_16 | - FE_CAN_QAM_32 | - FE_CAN_QAM_64 | - FE_CAN_QAM_128 | - FE_CAN_QAM_256 | - FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_HIERARCHY_AUTO | - FE_CAN_MUTE_TS | - FE_CAN_2G_MODULATION | - FE_CAN_MULTISTREAM - }, - - .get_tune_settings = mn88473_get_tune_settings, - - .init = mn88473_init, - .sleep = mn88473_sleep, - - .set_frontend = mn88473_set_frontend, - - .read_status = mn88473_read_status, -}; - -static int mn88473_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct mn88473_config *config = client->dev.platform_data; - struct mn88473_dev *dev; - int ret; - unsigned int utmp; - static const struct regmap_config regmap_config = { - .reg_bits = 8, - .val_bits = 8, - }; - - dev_dbg(&client->dev, "\n"); - - /* Caller really need to provide pointer for frontend we create. */ - if (config->fe == NULL) { - dev_err(&client->dev, "frontend pointer not defined\n"); - ret = -EINVAL; - goto err; - } - - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (dev == NULL) { - ret = -ENOMEM; - goto err; - } - - dev->i2c_wr_max = config->i2c_wr_max; - if (!config->xtal) - dev->xtal = 25000000; - else - dev->xtal = config->xtal; - dev->client[0] = client; - dev->regmap[0] = regmap_init_i2c(dev->client[0], ®map_config); - if (IS_ERR(dev->regmap[0])) { - ret = PTR_ERR(dev->regmap[0]); - goto err_kfree; - } - - /* check demod answers to I2C */ - ret = regmap_read(dev->regmap[0], 0x00, &utmp); - if (ret) - goto err_regmap_0_regmap_exit; - - /* - * Chip has three I2C addresses for different register pages. Used - * addresses are 0x18, 0x1a and 0x1c. We register two dummy clients, - * 0x1a and 0x1c, in order to get own I2C client for each register page. - */ - dev->client[1] = i2c_new_dummy(client->adapter, 0x1a); - if (dev->client[1] == NULL) { - ret = -ENODEV; - dev_err(&client->dev, "I2C registration failed\n"); - if (ret) - goto err_regmap_0_regmap_exit; - } - dev->regmap[1] = regmap_init_i2c(dev->client[1], ®map_config); - if (IS_ERR(dev->regmap[1])) { - ret = PTR_ERR(dev->regmap[1]); - goto err_client_1_i2c_unregister_device; - } - i2c_set_clientdata(dev->client[1], dev); - - dev->client[2] = i2c_new_dummy(client->adapter, 0x1c); - if (dev->client[2] == NULL) { - ret = -ENODEV; - dev_err(&client->dev, "2nd I2C registration failed\n"); - if (ret) - goto err_regmap_1_regmap_exit; - } - dev->regmap[2] = regmap_init_i2c(dev->client[2], ®map_config); - if (IS_ERR(dev->regmap[2])) { - ret = PTR_ERR(dev->regmap[2]); - goto err_client_2_i2c_unregister_device; - } - i2c_set_clientdata(dev->client[2], dev); - - /* create dvb_frontend */ - memcpy(&dev->fe.ops, &mn88473_ops, sizeof(struct dvb_frontend_ops)); - dev->fe.demodulator_priv = client; - *config->fe = &dev->fe; - i2c_set_clientdata(client, dev); - - dev_info(&dev->client[0]->dev, "Panasonic MN88473 successfully attached\n"); - return 0; - -err_client_2_i2c_unregister_device: - i2c_unregister_device(dev->client[2]); -err_regmap_1_regmap_exit: - regmap_exit(dev->regmap[1]); -err_client_1_i2c_unregister_device: - i2c_unregister_device(dev->client[1]); -err_regmap_0_regmap_exit: - regmap_exit(dev->regmap[0]); -err_kfree: - kfree(dev); -err: - dev_dbg(&client->dev, "failed=%d\n", ret); - return ret; -} - -static int mn88473_remove(struct i2c_client *client) -{ - struct mn88473_dev *dev = i2c_get_clientdata(client); - - dev_dbg(&client->dev, "\n"); - - regmap_exit(dev->regmap[2]); - i2c_unregister_device(dev->client[2]); - - regmap_exit(dev->regmap[1]); - i2c_unregister_device(dev->client[1]); - - regmap_exit(dev->regmap[0]); - - kfree(dev); - - return 0; -} - -static const struct i2c_device_id mn88473_id_table[] = { - {"mn88473", 0}, - {} -}; -MODULE_DEVICE_TABLE(i2c, mn88473_id_table); - -static struct i2c_driver mn88473_driver = { - .driver = { - .name = "mn88473", - }, - .probe = mn88473_probe, - .remove = mn88473_remove, - .id_table = mn88473_id_table, -}; - -module_i2c_driver(mn88473_driver); - -MODULE_AUTHOR("Antti Palosaari "); -MODULE_DESCRIPTION("Panasonic MN88473 DVB-T/T2/C demodulator driver"); -MODULE_LICENSE("GPL"); -/*(DEBLOBBED)*/ diff --git a/drivers/staging/media/mn88473/mn88473_priv.h b/drivers/staging/media/mn88473/mn88473_priv.h deleted file mode 100644 index 0706d7e60..000000000 --- a/drivers/staging/media/mn88473/mn88473_priv.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Panasonic MN88473 DVB-T/T2/C demodulator driver - * - * Copyright (C) 2014 Antti Palosaari - * - * 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. - */ - -#ifndef MN88473_PRIV_H -#define MN88473_PRIV_H - -#include "dvb_frontend.h" -#include "mn88473.h" -#include -#include - -#define MN88473_FIRMWARE "/*(DEBLOBBED)*/" - -struct mn88473_dev { - struct i2c_client *client[3]; - struct regmap *regmap[3]; - struct dvb_frontend fe; - u16 i2c_wr_max; - enum fe_delivery_system delivery_system; - bool warm; /* FW running */ - u32 xtal; -}; - -#endif diff --git a/drivers/staging/media/mx2/Kconfig b/drivers/staging/media/mx2/Kconfig new file mode 100644 index 000000000..beaa885cf --- /dev/null +++ b/drivers/staging/media/mx2/Kconfig @@ -0,0 +1,15 @@ +config VIDEO_MX2 + tristate "i.MX27 Camera Sensor Interface driver" + depends on VIDEO_DEV && SOC_CAMERA + depends on SOC_IMX27 || COMPILE_TEST + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + ---help--- + This is a v4l2 driver for the i.MX27 Camera Sensor Interface + + This driver is deprecated: it should become a stand-alone driver + instead of using the soc-camera framework. + + Unless someone is willing to take this on (unlikely with such + ancient hardware) it is going to be removed from the kernel + soon. diff --git a/drivers/staging/media/mx2/Makefile b/drivers/staging/media/mx2/Makefile new file mode 100644 index 000000000..fc5b2826a --- /dev/null +++ b/drivers/staging/media/mx2/Makefile @@ -0,0 +1,3 @@ +# Makefile for i.MX27 Camera Sensor driver + +obj-$(CONFIG_VIDEO_MX2) += mx2_camera.o diff --git a/drivers/staging/media/mx2/TODO b/drivers/staging/media/mx2/TODO new file mode 100644 index 000000000..bc68fa443 --- /dev/null +++ b/drivers/staging/media/mx2/TODO @@ -0,0 +1,10 @@ +This driver is deprecated: it should become a stand-alone driver instead of +using the soc-camera framework. + +Unless someone is willing to take this on (unlikely with such ancient +hardware) it is going to be removed from the kernel soon. + +Note that trivial patches will not be accepted anymore, only a full conversion. + +If you want to convert this driver, please contact the linux-media mailinglist +(see http://linuxtv.org/lists.php). diff --git a/drivers/staging/media/mx2/mx2_camera.c b/drivers/staging/media/mx2/mx2_camera.c new file mode 100644 index 000000000..48dd5b785 --- /dev/null +++ b/drivers/staging/media/mx2/mx2_camera.c @@ -0,0 +1,1636 @@ +/* + * V4L2 Driver for i.MX27 camera host + * + * Copyright (C) 2008, Sascha Hauer, Pengutronix + * Copyright (C) 2010, Baruch Siach, Orex Computed Radiography + * Copyright (C) 2012, Javier Martin, Vista Silicon S.L. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#define MX2_CAM_DRV_NAME "mx2-camera" +#define MX2_CAM_VERSION "0.0.6" +#define MX2_CAM_DRIVER_DESCRIPTION "i.MX2x_Camera" + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define CSICR1_SWAP16_EN (1 << 31) +#define CSICR1_EXT_VSYNC (1 << 30) +#define CSICR1_EOF_INTEN (1 << 29) +#define CSICR1_PRP_IF_EN (1 << 28) +#define CSICR1_CCIR_MODE (1 << 27) +#define CSICR1_COF_INTEN (1 << 26) +#define CSICR1_SF_OR_INTEN (1 << 25) +#define CSICR1_RF_OR_INTEN (1 << 24) +#define CSICR1_STATFF_LEVEL (3 << 22) +#define CSICR1_STATFF_INTEN (1 << 21) +#define CSICR1_RXFF_LEVEL(l) (((l) & 3) << 19) +#define CSICR1_RXFF_INTEN (1 << 18) +#define CSICR1_SOF_POL (1 << 17) +#define CSICR1_SOF_INTEN (1 << 16) +#define CSICR1_MCLKDIV(d) (((d) & 0xF) << 12) +#define CSICR1_HSYNC_POL (1 << 11) +#define CSICR1_CCIR_EN (1 << 10) +#define CSICR1_MCLKEN (1 << 9) +#define CSICR1_FCC (1 << 8) +#define CSICR1_PACK_DIR (1 << 7) +#define CSICR1_CLR_STATFIFO (1 << 6) +#define CSICR1_CLR_RXFIFO (1 << 5) +#define CSICR1_GCLK_MODE (1 << 4) +#define CSICR1_INV_DATA (1 << 3) +#define CSICR1_INV_PCLK (1 << 2) +#define CSICR1_REDGE (1 << 1) +#define CSICR1_FMT_MASK (CSICR1_PACK_DIR | CSICR1_SWAP16_EN) + +#define SHIFT_STATFF_LEVEL 22 +#define SHIFT_RXFF_LEVEL 19 +#define SHIFT_MCLKDIV 12 + +#define SHIFT_FRMCNT 16 + +#define CSICR1 0x00 +#define CSICR2 0x04 +#define CSISR 0x08 +#define CSISTATFIFO 0x0c +#define CSIRFIFO 0x10 +#define CSIRXCNT 0x14 +#define CSICR3 0x1c +#define CSIDMASA_STATFIFO 0x20 +#define CSIDMATA_STATFIFO 0x24 +#define CSIDMASA_FB1 0x28 +#define CSIDMASA_FB2 0x2c +#define CSIFBUF_PARA 0x30 +#define CSIIMAG_PARA 0x34 + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1 << 31) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +/* Resizing registers */ +#define PRP_RZ_VALID_TBL_LEN(x) ((x) << 24) +#define PRP_RZ_VALID_BILINEAR (1 << 31) + +#define MAX_VIDEO_MEM 16 + +#define RESIZE_NUM_MIN 1 +#define RESIZE_NUM_MAX 20 +#define BC_COEF 3 +#define SZ_COEF (1 << BC_COEF) + +#define RESIZE_DIR_H 0 +#define RESIZE_DIR_V 1 + +#define RESIZE_ALGO_BILINEAR 0 +#define RESIZE_ALGO_AVERAGING 1 + +struct mx2_prp_cfg { + int channel; + u32 in_fmt; + u32 out_fmt; + u32 src_pixel; + u32 ch1_pixel; + u32 irq_flags; + u32 csicr1; +}; + +/* prp resizing parameters */ +struct emma_prp_resize { + int algo; /* type of algorithm used */ + int len; /* number of coefficients */ + unsigned char s[RESIZE_NUM_MAX]; /* table of coefficients */ +}; + +/* prp configuration for a client-host fmt pair */ +struct mx2_fmt_cfg { + u32 in_fmt; + u32 out_fmt; + struct mx2_prp_cfg cfg; +}; + +struct mx2_buf_internal { + struct list_head queue; + int bufnum; + bool discard; +}; + +/* buffer for one video frame */ +struct mx2_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct mx2_buf_internal internal; +}; + +enum mx2_camera_type { + IMX27_CAMERA, +}; + +struct mx2_camera_dev { + struct device *dev; + struct soc_camera_host soc_host; + struct clk *clk_emma_ahb, *clk_emma_ipg; + struct clk *clk_csi_ahb, *clk_csi_per; + + void __iomem *base_csi, *base_emma; + + struct mx2_camera_platform_data *pdata; + unsigned long platform_flags; + + struct list_head capture; + struct list_head active_bufs; + struct list_head discard; + + spinlock_t lock; + + int dma; + struct mx2_buffer *active; + struct mx2_buffer *fb1_active; + struct mx2_buffer *fb2_active; + + u32 csicr1; + enum mx2_camera_type devtype; + + struct mx2_buf_internal buf_discard[2]; + void *discard_buffer; + dma_addr_t discard_buffer_dma; + size_t discard_size; + struct mx2_fmt_cfg *emma_prp; + struct emma_prp_resize resizing[2]; + unsigned int s_width, s_height; + u32 frame_count; + struct vb2_alloc_ctx *alloc_ctx; +}; + +static struct platform_device_id mx2_camera_devtype[] = { + { + .name = "imx27-camera", + .driver_data = IMX27_CAMERA, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mx2_camera_devtype); + +static struct mx2_buffer *mx2_ibuf_to_buf(struct mx2_buf_internal *int_buf) +{ + return container_of(int_buf, struct mx2_buffer, internal); +} + +static struct mx2_fmt_cfg mx27_emma_prp_table[] = { + /* + * This is a generic configuration which is valid for most + * prp input-output format combinations. + * We set the incoming and outgoing pixelformat to a + * 16 Bit wide format and adjust the bytesperline + * accordingly. With this configuration the inputdata + * will not be changed by the emma and could be any type + * of 16 Bit Pixelformat. + */ + { + .in_fmt = 0, + .out_fmt = 0, + .cfg = { + .channel = 1, + .in_fmt = PRP_CNTL_DATA_IN_RGB16, + .out_fmt = PRP_CNTL_CH1_OUT_RGB16, + .src_pixel = 0x2ca00565, /* RGB565 */ + .ch1_pixel = 0x2ca00565, /* RGB565 */ + .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH1WERR | + PRP_INTR_CH1FC | PRP_INTR_LBOVF, + .csicr1 = 0, + } + }, + { + .in_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .out_fmt = V4L2_PIX_FMT_YUYV, + .cfg = { + .channel = 1, + .in_fmt = PRP_CNTL_DATA_IN_YUV422, + .out_fmt = PRP_CNTL_CH1_OUT_YUV422, + .src_pixel = 0x22000888, /* YUV422 (YUYV) */ + .ch1_pixel = 0x62000888, /* YUV422 (YUYV) */ + .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH1WERR | + PRP_INTR_CH1FC | PRP_INTR_LBOVF, + .csicr1 = CSICR1_SWAP16_EN, + } + }, + { + .in_fmt = MEDIA_BUS_FMT_YUYV8_2X8, + .out_fmt = V4L2_PIX_FMT_YUYV, + .cfg = { + .channel = 1, + .in_fmt = PRP_CNTL_DATA_IN_YUV422, + .out_fmt = PRP_CNTL_CH1_OUT_YUV422, + .src_pixel = 0x22000888, /* YUV422 (YUYV) */ + .ch1_pixel = 0x62000888, /* YUV422 (YUYV) */ + .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH1WERR | + PRP_INTR_CH1FC | PRP_INTR_LBOVF, + .csicr1 = CSICR1_PACK_DIR, + } + }, + { + .in_fmt = MEDIA_BUS_FMT_YUYV8_2X8, + .out_fmt = V4L2_PIX_FMT_YUV420, + .cfg = { + .channel = 2, + .in_fmt = PRP_CNTL_DATA_IN_YUV422, + .out_fmt = PRP_CNTL_CH2_OUT_YUV420, + .src_pixel = 0x22000888, /* YUV422 (YUYV) */ + .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH2WERR | + PRP_INTR_CH2FC | PRP_INTR_LBOVF | + PRP_INTR_CH2OVF, + .csicr1 = CSICR1_PACK_DIR, + } + }, + { + .in_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .out_fmt = V4L2_PIX_FMT_YUV420, + .cfg = { + .channel = 2, + .in_fmt = PRP_CNTL_DATA_IN_YUV422, + .out_fmt = PRP_CNTL_CH2_OUT_YUV420, + .src_pixel = 0x22000888, /* YUV422 (YUYV) */ + .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH2WERR | + PRP_INTR_CH2FC | PRP_INTR_LBOVF | + PRP_INTR_CH2OVF, + .csicr1 = CSICR1_SWAP16_EN, + } + }, +}; + +static struct mx2_fmt_cfg *mx27_emma_prp_get_format(u32 in_fmt, u32 out_fmt) +{ + int i; + + for (i = 1; i < ARRAY_SIZE(mx27_emma_prp_table); i++) + if ((mx27_emma_prp_table[i].in_fmt == in_fmt) && + (mx27_emma_prp_table[i].out_fmt == out_fmt)) { + return &mx27_emma_prp_table[i]; + } + /* If no match return the most generic configuration */ + return &mx27_emma_prp_table[0]; +}; + +static void mx27_update_emma_buf(struct mx2_camera_dev *pcdev, + unsigned long phys, int bufnum) +{ + struct mx2_fmt_cfg *prp = pcdev->emma_prp; + + if (prp->cfg.channel == 1) { + writel(phys, pcdev->base_emma + + PRP_DEST_RGB1_PTR + 4 * bufnum); + } else { + writel(phys, pcdev->base_emma + + PRP_DEST_Y_PTR - 0x14 * bufnum); + if (prp->out_fmt == V4L2_PIX_FMT_YUV420) { + u32 imgsize = pcdev->soc_host.icd->user_height * + pcdev->soc_host.icd->user_width; + + writel(phys + imgsize, pcdev->base_emma + + PRP_DEST_CB_PTR - 0x14 * bufnum); + writel(phys + ((5 * imgsize) / 4), pcdev->base_emma + + PRP_DEST_CR_PTR - 0x14 * bufnum); + } + } +} + +static void mx2_camera_deactivate(struct mx2_camera_dev *pcdev) +{ + clk_disable_unprepare(pcdev->clk_csi_ahb); + clk_disable_unprepare(pcdev->clk_csi_per); + writel(0, pcdev->base_csi + CSICR1); + writel(0, pcdev->base_emma + PRP_CNTL); +} + +static int mx2_camera_add_device(struct soc_camera_device *icd) +{ + dev_info(icd->parent, "Camera driver attached to camera %d\n", + icd->devnum); + + return 0; +} + +static void mx2_camera_remove_device(struct soc_camera_device *icd) +{ + dev_info(icd->parent, "Camera driver detached from camera %d\n", + icd->devnum); +} + +/* + * The following two functions absolutely depend on the fact, that + * there can be only one camera on mx2 camera sensor interface + */ +static int mx2_camera_clock_start(struct soc_camera_host *ici) +{ + struct mx2_camera_dev *pcdev = ici->priv; + int ret; + u32 csicr1; + + ret = clk_prepare_enable(pcdev->clk_csi_ahb); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(pcdev->clk_csi_per); + if (ret < 0) + goto exit_csi_ahb; + + csicr1 = CSICR1_MCLKEN | CSICR1_PRP_IF_EN | CSICR1_FCC | + CSICR1_RXFF_LEVEL(0); + + pcdev->csicr1 = csicr1; + writel(pcdev->csicr1, pcdev->base_csi + CSICR1); + + pcdev->frame_count = 0; + + return 0; + +exit_csi_ahb: + clk_disable_unprepare(pcdev->clk_csi_ahb); + + return ret; +} + +static void mx2_camera_clock_stop(struct soc_camera_host *ici) +{ + struct mx2_camera_dev *pcdev = ici->priv; + + mx2_camera_deactivate(pcdev); +} + +/* + * Videobuf operations + */ +static int mx2_videobuf_setup(struct vb2_queue *vq, + unsigned int *count, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + + dev_dbg(icd->parent, "count=%d, size=%d\n", *count, sizes[0]); + + alloc_ctxs[0] = pcdev->alloc_ctx; + + sizes[0] = icd->sizeimage; + + if (0 == *count) + *count = 32; + if (!*num_planes && + sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024) + *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0]; + + *num_planes = 1; + + return 0; +} + +static int mx2_videobuf_prepare(struct vb2_buffer *vb) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + int ret = 0; + + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); + +#ifdef DEBUG + /* + * This can be useful if you want to see if we actually fill + * the buffer with something + */ + memset((void *)vb2_plane_vaddr(vb, 0), + 0xaa, vb2_get_plane_payload(vb, 0)); +#endif + + vb2_set_plane_payload(vb, 0, icd->sizeimage); + if (vb2_plane_vaddr(vb, 0) && + vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) { + ret = -EINVAL; + goto out; + } + + return 0; + +out: + return ret; +} + +static void mx2_videobuf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = + to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_buffer *buf = container_of(vbuf, struct mx2_buffer, vb); + unsigned long flags; + + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); + + spin_lock_irqsave(&pcdev->lock, flags); + + list_add_tail(&buf->internal.queue, &pcdev->capture); + + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static void mx27_camera_emma_buf_init(struct soc_camera_device *icd, + int bytesperline) +{ + struct soc_camera_host *ici = + to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_fmt_cfg *prp = pcdev->emma_prp; + + writel((pcdev->s_width << 16) | pcdev->s_height, + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + writel(prp->cfg.src_pixel, + pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL); + if (prp->cfg.channel == 1) { + writel((icd->user_width << 16) | icd->user_height, + pcdev->base_emma + PRP_CH1_OUT_IMAGE_SIZE); + writel(bytesperline, + pcdev->base_emma + PRP_DEST_CH1_LINE_STRIDE); + writel(prp->cfg.ch1_pixel, + pcdev->base_emma + PRP_CH1_PIXEL_FORMAT_CNTL); + } else { /* channel 2 */ + writel((icd->user_width << 16) | icd->user_height, + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + } + + /* Enable interrupts */ + writel(prp->cfg.irq_flags, pcdev->base_emma + PRP_INTR_CNTL); +} + +static void mx2_prp_resize_commit(struct mx2_camera_dev *pcdev) +{ + int dir; + + for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) { + unsigned char *s = pcdev->resizing[dir].s; + int len = pcdev->resizing[dir].len; + unsigned int coeff[2] = {0, 0}; + unsigned int valid = 0; + int i; + + if (len == 0) + continue; + + for (i = RESIZE_NUM_MAX - 1; i >= 0; i--) { + int j; + + j = i > 9 ? 1 : 0; + coeff[j] = (coeff[j] << BC_COEF) | + (s[i] & (SZ_COEF - 1)); + + if (i == 5 || i == 15) + coeff[j] <<= 1; + + valid = (valid << 1) | (s[i] >> BC_COEF); + } + + valid |= PRP_RZ_VALID_TBL_LEN(len); + + if (pcdev->resizing[dir].algo == RESIZE_ALGO_BILINEAR) + valid |= PRP_RZ_VALID_BILINEAR; + + if (pcdev->emma_prp->cfg.channel == 1) { + if (dir == RESIZE_DIR_H) { + writel(coeff[0], pcdev->base_emma + + PRP_CH1_RZ_HORI_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH1_RZ_HORI_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH1_RZ_HORI_VALID); + } else { + writel(coeff[0], pcdev->base_emma + + PRP_CH1_RZ_VERT_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH1_RZ_VERT_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH1_RZ_VERT_VALID); + } + } else { + if (dir == RESIZE_DIR_H) { + writel(coeff[0], pcdev->base_emma + + PRP_CH2_RZ_HORI_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH2_RZ_HORI_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH2_RZ_HORI_VALID); + } else { + writel(coeff[0], pcdev->base_emma + + PRP_CH2_RZ_VERT_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH2_RZ_VERT_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH2_RZ_VERT_VALID); + } + } + } +} + +static int mx2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(q); + struct soc_camera_host *ici = + to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_fmt_cfg *prp = pcdev->emma_prp; + struct vb2_buffer *vb; + struct mx2_buffer *buf; + unsigned long phys; + int bytesperline; + unsigned long flags; + + if (count < 2) + return -ENOBUFS; + + spin_lock_irqsave(&pcdev->lock, flags); + + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); + buf->internal.bufnum = 0; + vb = &buf->vb.vb2_buf; + + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum); + list_move_tail(pcdev->capture.next, &pcdev->active_bufs); + + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); + buf->internal.bufnum = 1; + vb = &buf->vb.vb2_buf; + + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum); + list_move_tail(pcdev->capture.next, &pcdev->active_bufs); + + bytesperline = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + if (bytesperline < 0) { + spin_unlock_irqrestore(&pcdev->lock, flags); + return bytesperline; + } + + /* + * I didn't manage to properly enable/disable the prp + * on a per frame basis during running transfers, + * thus we allocate a buffer here and use it to + * discard frames when no buffer is available. + * Feel free to work on this ;) + */ + pcdev->discard_size = icd->user_height * bytesperline; + pcdev->discard_buffer = dma_alloc_coherent(ici->v4l2_dev.dev, + pcdev->discard_size, + &pcdev->discard_buffer_dma, GFP_ATOMIC); + if (!pcdev->discard_buffer) { + spin_unlock_irqrestore(&pcdev->lock, flags); + return -ENOMEM; + } + + pcdev->buf_discard[0].discard = true; + list_add_tail(&pcdev->buf_discard[0].queue, + &pcdev->discard); + + pcdev->buf_discard[1].discard = true; + list_add_tail(&pcdev->buf_discard[1].queue, + &pcdev->discard); + + mx2_prp_resize_commit(pcdev); + + mx27_camera_emma_buf_init(icd, bytesperline); + + if (prp->cfg.channel == 1) { + writel(PRP_CNTL_CH1EN | + PRP_CNTL_CSIEN | + prp->cfg.in_fmt | + prp->cfg.out_fmt | + PRP_CNTL_CH1_LEN | + PRP_CNTL_CH1BYP | + PRP_CNTL_CH1_TSKIP(0) | + PRP_CNTL_IN_TSKIP(0), + pcdev->base_emma + PRP_CNTL); + } else { + writel(PRP_CNTL_CH2EN | + PRP_CNTL_CSIEN | + prp->cfg.in_fmt | + prp->cfg.out_fmt | + PRP_CNTL_CH2_LEN | + PRP_CNTL_CH2_TSKIP(0) | + PRP_CNTL_IN_TSKIP(0), + pcdev->base_emma + PRP_CNTL); + } + spin_unlock_irqrestore(&pcdev->lock, flags); + + return 0; +} + +static void mx2_stop_streaming(struct vb2_queue *q) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(q); + struct soc_camera_host *ici = + to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_fmt_cfg *prp = pcdev->emma_prp; + unsigned long flags; + void *b; + u32 cntl; + + spin_lock_irqsave(&pcdev->lock, flags); + + cntl = readl(pcdev->base_emma + PRP_CNTL); + if (prp->cfg.channel == 1) { + writel(cntl & ~PRP_CNTL_CH1EN, + pcdev->base_emma + PRP_CNTL); + } else { + writel(cntl & ~PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); + } + INIT_LIST_HEAD(&pcdev->capture); + INIT_LIST_HEAD(&pcdev->active_bufs); + INIT_LIST_HEAD(&pcdev->discard); + + b = pcdev->discard_buffer; + pcdev->discard_buffer = NULL; + + spin_unlock_irqrestore(&pcdev->lock, flags); + + dma_free_coherent(ici->v4l2_dev.dev, + pcdev->discard_size, b, pcdev->discard_buffer_dma); +} + +static struct vb2_ops mx2_videobuf_ops = { + .queue_setup = mx2_videobuf_setup, + .buf_prepare = mx2_videobuf_prepare, + .buf_queue = mx2_videobuf_queue, + .start_streaming = mx2_start_streaming, + .stop_streaming = mx2_stop_streaming, +}; + +static int mx2_camera_init_videobuf(struct vb2_queue *q, + struct soc_camera_device *icd) +{ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = icd; + q->ops = &mx2_videobuf_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct mx2_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + return vb2_queue_init(q); +} + +#define MX2_BUS_FLAGS (V4L2_MBUS_MASTER | \ + V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_VSYNC_ACTIVE_LOW | \ + V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_HSYNC_ACTIVE_LOW | \ + V4L2_MBUS_PCLK_SAMPLE_RISING | \ + V4L2_MBUS_PCLK_SAMPLE_FALLING | \ + V4L2_MBUS_DATA_ACTIVE_HIGH | \ + V4L2_MBUS_DATA_ACTIVE_LOW) + +static int mx27_camera_emma_prp_reset(struct mx2_camera_dev *pcdev) +{ + int count = 0; + + readl(pcdev->base_emma + PRP_CNTL); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + while (count++ < 100) { + if (!(readl(pcdev->base_emma + PRP_CNTL) & PRP_CNTL_SWRST)) + return 0; + barrier(); + udelay(1); + } + + return -ETIMEDOUT; +} + +static int mx2_camera_set_bus_param(struct soc_camera_device *icd) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; + unsigned long common_flags; + int ret; + int bytesperline; + u32 csicr1 = pcdev->csicr1; + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (!ret) { + common_flags = soc_mbus_config_compatible(&cfg, MX2_BUS_FLAGS); + if (!common_flags) { + dev_warn(icd->parent, + "Flags incompatible: camera 0x%x, host 0x%x\n", + cfg.flags, MX2_BUS_FLAGS); + return -EINVAL; + } + } else if (ret != -ENOIOCTLCMD) { + return ret; + } else { + common_flags = MX2_BUS_FLAGS; + } + + if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) { + if (pcdev->platform_flags & MX2_CAMERA_HSYNC_HIGH) + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW; + else + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH; + } + + if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) && + (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) { + if (pcdev->platform_flags & MX2_CAMERA_PCLK_SAMPLE_RISING) + common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING; + else + common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING; + } + + cfg.flags = common_flags; + ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(icd->parent, "camera s_mbus_config(0x%lx) returned %d\n", + common_flags, ret); + return ret; + } + + csicr1 = (csicr1 & ~CSICR1_FMT_MASK) | pcdev->emma_prp->cfg.csicr1; + + if (common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + csicr1 |= CSICR1_REDGE; + if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + csicr1 |= CSICR1_SOF_POL; + if (common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + csicr1 |= CSICR1_HSYNC_POL; + if (pcdev->platform_flags & MX2_CAMERA_EXT_VSYNC) + csicr1 |= CSICR1_EXT_VSYNC; + if (pcdev->platform_flags & MX2_CAMERA_CCIR) + csicr1 |= CSICR1_CCIR_EN; + if (pcdev->platform_flags & MX2_CAMERA_CCIR_INTERLACE) + csicr1 |= CSICR1_CCIR_MODE; + if (pcdev->platform_flags & MX2_CAMERA_GATED_CLOCK) + csicr1 |= CSICR1_GCLK_MODE; + if (pcdev->platform_flags & MX2_CAMERA_INV_DATA) + csicr1 |= CSICR1_INV_DATA; + + pcdev->csicr1 = csicr1; + + bytesperline = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + if (bytesperline < 0) + return bytesperline; + + ret = mx27_camera_emma_prp_reset(pcdev); + if (ret) + return ret; + + writel(pcdev->csicr1, pcdev->base_csi + CSICR1); + + return 0; +} + +static int mx2_camera_set_crop(struct soc_camera_device *icd, + const struct v4l2_crop *a) +{ + struct v4l2_crop a_writable = *a; + struct v4l2_rect *rect = &a_writable.c; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &fmt.format; + int ret; + + soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096); + soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096); + + ret = v4l2_subdev_call(sd, video, s_crop, a); + if (ret < 0) + return ret; + + /* The capture device might have changed its output */ + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret < 0) + return ret; + + dev_dbg(icd->parent, "Sensor cropped %dx%d\n", + mf->width, mf->height); + + icd->user_width = mf->width; + icd->user_height = mf->height; + + return ret; +} + +static int mx2_camera_get_formats(struct soc_camera_device *icd, + unsigned int idx, + struct soc_camera_format_xlate *xlate) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_mbus_pixelfmt *fmt; + struct device *dev = icd->parent; + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .index = idx, + }; + int ret, formats = 0; + + ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code); + if (ret < 0) + /* no more formats */ + return 0; + + fmt = soc_mbus_get_fmtdesc(code.code); + if (!fmt) { + dev_err(dev, "Invalid format code #%u: %d\n", idx, code.code); + return 0; + } + + if (code.code == MEDIA_BUS_FMT_YUYV8_2X8 || + code.code == MEDIA_BUS_FMT_UYVY8_2X8) { + formats++; + if (xlate) { + /* + * CH2 can output YUV420 which is a standard format in + * soc_mediabus.c + */ + xlate->host_fmt = + soc_mbus_get_fmtdesc(MEDIA_BUS_FMT_YUYV8_1_5X8); + xlate->code = code.code; + dev_dbg(dev, "Providing host format %s for sensor code %d\n", + xlate->host_fmt->name, code.code); + xlate++; + } + } + + if (code.code == MEDIA_BUS_FMT_UYVY8_2X8) { + formats++; + if (xlate) { + xlate->host_fmt = + soc_mbus_get_fmtdesc(MEDIA_BUS_FMT_YUYV8_2X8); + xlate->code = code.code; + dev_dbg(dev, "Providing host format %s for sensor code %d\n", + xlate->host_fmt->name, code.code); + xlate++; + } + } + + /* Generic pass-trough */ + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code.code; + xlate++; + } + return formats; +} + +static int mx2_emmaprp_resize(struct mx2_camera_dev *pcdev, + struct v4l2_mbus_framefmt *mf_in, + struct v4l2_pix_format *pix_out, bool apply) +{ + unsigned int num, den; + unsigned long m; + int i, dir; + + for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) { + struct emma_prp_resize tmprsz; + unsigned char *s = tmprsz.s; + int len = 0; + int in, out; + + if (dir == RESIZE_DIR_H) { + in = mf_in->width; + out = pix_out->width; + } else { + in = mf_in->height; + out = pix_out->height; + } + + if (in < out) + return -EINVAL; + else if (in == out) + continue; + + /* Calculate ratio */ + m = gcd(in, out); + num = in / m; + den = out / m; + if (num > RESIZE_NUM_MAX) + return -EINVAL; + + if ((num >= 2 * den) && (den == 1) && + (num < 9) && (!(num & 0x01))) { + int sum = 0; + int j; + + /* Average scaling for >= 2:1 ratios */ + /* Support can be added for num >=9 and odd values */ + + tmprsz.algo = RESIZE_ALGO_AVERAGING; + len = num; + + for (i = 0; i < (len / 2); i++) + s[i] = 8; + + do { + for (i = 0; i < (len / 2); i++) { + s[i] = s[i] >> 1; + sum = 0; + for (j = 0; j < (len / 2); j++) + sum += s[j]; + if (sum == 4) + break; + } + } while (sum != 4); + + for (i = (len / 2); i < len; i++) + s[i] = s[len - i - 1]; + + s[len - 1] |= SZ_COEF; + } else { + /* bilinear scaling for < 2:1 ratios */ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + int in_pos_inc = 2 * den; + int out_pos = num; + int out_pos_inc = 2 * num; + int init_carry = num - den; + int carry = init_carry; + + tmprsz.algo = RESIZE_ALGO_BILINEAR; + v = den + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + + if (len > RESIZE_NUM_MAX) + return -EINVAL; + + coeff = ((coeff << BC_COEF) + + (in_pos_inc >> 1)) / in_pos_inc; + + if (coeff >= (SZ_COEF - 1)) + coeff--; + + coeff |= SZ_COEF; + s[len] = (unsigned char)coeff; + len++; + + for (i = 1; i < nxt; i++) { + if (len >= RESIZE_NUM_MAX) + return -EINVAL; + s[len] = 0; + len++; + } + } while (carry != init_carry); + } + tmprsz.len = len; + if (dir == RESIZE_DIR_H) + mf_in->width = pix_out->width; + else + mf_in->height = pix_out->height; + + if (apply) + memcpy(&pcdev->resizing[dir], &tmprsz, sizeof(tmprsz)); + } + return 0; +} + +static int mx2_camera_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + int ret; + + dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); + if (!xlate) { + dev_warn(icd->parent, "Format %x not found\n", + pix->pixelformat); + return -EINVAL; + } + + mf->width = pix->width; + mf->height = pix->height; + mf->field = pix->field; + mf->colorspace = pix->colorspace; + mf->code = xlate->code; + + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &format); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + /* Store width and height returned by the sensor for resizing */ + pcdev->s_width = mf->width; + pcdev->s_height = mf->height; + dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n", + __func__, pcdev->s_width, pcdev->s_height); + + pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code, + xlate->host_fmt->fourcc); + + memset(pcdev->resizing, 0, sizeof(pcdev->resizing)); + if ((mf->width != pix->width || mf->height != pix->height) && + pcdev->emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) { + if (mx2_emmaprp_resize(pcdev, mf, pix, true) < 0) + dev_dbg(icd->parent, "%s: can't resize\n", __func__); + } + + if (mf->code != xlate->code) + return -EINVAL; + + pix->width = mf->width; + pix->height = mf->height; + pix->field = mf->field; + pix->colorspace = mf->colorspace; + icd->current_fmt = xlate; + + dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + + return 0; +} + +static int mx2_camera_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + __u32 pixfmt = pix->pixelformat; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_fmt_cfg *emma_prp; + int ret; + + dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (pixfmt && !xlate) { + dev_warn(icd->parent, "Format %x not found\n", pixfmt); + return -EINVAL; + } + + /* + * limit to MX27 hardware capabilities: width must be a multiple of 8 as + * requested by the CSI. (Table 39-2 in the i.MX27 Reference Manual). + */ + pix->width &= ~0x7; + + /* limit to sensor capabilities */ + mf->width = pix->width; + mf->height = pix->height; + mf->field = pix->field; + mf->colorspace = pix->colorspace; + mf->code = xlate->code; + + ret = v4l2_subdev_call(sd, pad, set_fmt, &pad_cfg, &format); + if (ret < 0) + return ret; + + dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n", + __func__, pcdev->s_width, pcdev->s_height); + + /* If the sensor does not support image size try PrP resizing */ + emma_prp = mx27_emma_prp_get_format(xlate->code, + xlate->host_fmt->fourcc); + + if ((mf->width != pix->width || mf->height != pix->height) && + emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) { + if (mx2_emmaprp_resize(pcdev, mf, pix, false) < 0) + dev_dbg(icd->parent, "%s: can't resize\n", __func__); + } + + if (mf->field == V4L2_FIELD_ANY) + mf->field = V4L2_FIELD_NONE; + /* + * Driver supports interlaced images provided they have + * both fields so that they can be processed as if they + * were progressive. + */ + if (mf->field != V4L2_FIELD_NONE && !V4L2_FIELD_HAS_BOTH(mf->field)) { + dev_err(icd->parent, "Field type %d unsupported.\n", + mf->field); + return -EINVAL; + } + + pix->width = mf->width; + pix->height = mf->height; + pix->field = mf->field; + pix->colorspace = mf->colorspace; + + dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + + return 0; +} + +static int mx2_camera_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + /* cap->name is set by the friendly caller:-> */ + strlcpy(cap->card, MX2_CAM_DRIVER_DESCRIPTION, sizeof(cap->card)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static unsigned int mx2_camera_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_device *icd = file->private_data; + + return vb2_poll(&icd->vb2_vidq, file, pt); +} + +static struct soc_camera_host_ops mx2_soc_camera_host_ops = { + .owner = THIS_MODULE, + .add = mx2_camera_add_device, + .remove = mx2_camera_remove_device, + .clock_start = mx2_camera_clock_start, + .clock_stop = mx2_camera_clock_stop, + .set_fmt = mx2_camera_set_fmt, + .set_crop = mx2_camera_set_crop, + .get_formats = mx2_camera_get_formats, + .try_fmt = mx2_camera_try_fmt, + .init_videobuf2 = mx2_camera_init_videobuf, + .poll = mx2_camera_poll, + .querycap = mx2_camera_querycap, + .set_bus_param = mx2_camera_set_bus_param, +}; + +static void mx27_camera_frame_done_emma(struct mx2_camera_dev *pcdev, + int bufnum, bool err) +{ +#ifdef DEBUG + struct mx2_fmt_cfg *prp = pcdev->emma_prp; +#endif + struct mx2_buf_internal *ibuf; + struct mx2_buffer *buf; + struct vb2_buffer *vb; + struct vb2_v4l2_buffer *vbuf; + unsigned long phys; + + ibuf = list_first_entry(&pcdev->active_bufs, struct mx2_buf_internal, + queue); + + BUG_ON(ibuf->bufnum != bufnum); + + if (ibuf->discard) { + /* + * Discard buffer must not be returned to user space. + * Just return it to the discard queue. + */ + list_move_tail(pcdev->active_bufs.next, &pcdev->discard); + } else { + buf = mx2_ibuf_to_buf(ibuf); + + vb = &buf->vb.vb2_buf; + vbuf = to_vb2_v4l2_buffer(vb); +#ifdef DEBUG + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + if (prp->cfg.channel == 1) { + if (readl(pcdev->base_emma + PRP_DEST_RGB1_PTR + + 4 * bufnum) != phys) { + dev_err(pcdev->dev, "%lx != %x\n", phys, + readl(pcdev->base_emma + + PRP_DEST_RGB1_PTR + 4 * bufnum)); + } + } else { + if (readl(pcdev->base_emma + PRP_DEST_Y_PTR - + 0x14 * bufnum) != phys) { + dev_err(pcdev->dev, "%lx != %x\n", phys, + readl(pcdev->base_emma + + PRP_DEST_Y_PTR - 0x14 * bufnum)); + } + } +#endif + dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb, + vb2_plane_vaddr(vb, 0), + vb2_get_plane_payload(vb, 0)); + + list_del_init(&buf->internal.queue); + vb->timestamp = ktime_get_ns(); + vbuf->sequence = pcdev->frame_count; + if (err) + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + else + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } + + pcdev->frame_count++; + + if (list_empty(&pcdev->capture)) { + if (list_empty(&pcdev->discard)) { + dev_warn(pcdev->dev, "%s: trying to access empty discard list\n", + __func__); + return; + } + + ibuf = list_first_entry(&pcdev->discard, + struct mx2_buf_internal, queue); + ibuf->bufnum = bufnum; + + list_move_tail(pcdev->discard.next, &pcdev->active_bufs); + mx27_update_emma_buf(pcdev, pcdev->discard_buffer_dma, bufnum); + return; + } + + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); + + buf->internal.bufnum = bufnum; + + list_move_tail(pcdev->capture.next, &pcdev->active_bufs); + + vb = &buf->vb.vb2_buf; + + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + mx27_update_emma_buf(pcdev, phys, bufnum); +} + +static irqreturn_t mx27_camera_emma_irq(int irq_emma, void *data) +{ + struct mx2_camera_dev *pcdev = data; + unsigned int status = readl(pcdev->base_emma + PRP_INTRSTATUS); + struct mx2_buf_internal *ibuf; + + spin_lock(&pcdev->lock); + + if (list_empty(&pcdev->active_bufs)) { + dev_warn(pcdev->dev, "%s: called while active list is empty\n", + __func__); + + if (!status) { + spin_unlock(&pcdev->lock); + return IRQ_NONE; + } + } + + if (status & (1 << 7)) { /* overflow */ + u32 cntl = readl(pcdev->base_emma + PRP_CNTL); + writel(cntl & ~(PRP_CNTL_CH1EN | PRP_CNTL_CH2EN), + pcdev->base_emma + PRP_CNTL); + writel(cntl, pcdev->base_emma + PRP_CNTL); + + ibuf = list_first_entry(&pcdev->active_bufs, + struct mx2_buf_internal, queue); + mx27_camera_frame_done_emma(pcdev, + ibuf->bufnum, true); + + status &= ~(1 << 7); + } else if (((status & (3 << 5)) == (3 << 5)) || + ((status & (3 << 3)) == (3 << 3))) { + /* + * Both buffers have triggered, process the one we're expecting + * to first + */ + ibuf = list_first_entry(&pcdev->active_bufs, + struct mx2_buf_internal, queue); + mx27_camera_frame_done_emma(pcdev, ibuf->bufnum, false); + status &= ~(1 << (6 - ibuf->bufnum)); /* mark processed */ + } else if ((status & (1 << 6)) || (status & (1 << 4))) { + mx27_camera_frame_done_emma(pcdev, 0, false); + } else if ((status & (1 << 5)) || (status & (1 << 3))) { + mx27_camera_frame_done_emma(pcdev, 1, false); + } + + spin_unlock(&pcdev->lock); + writel(status, pcdev->base_emma + PRP_INTRSTATUS); + + return IRQ_HANDLED; +} + +static int mx27_camera_emma_init(struct platform_device *pdev) +{ + struct mx2_camera_dev *pcdev = platform_get_drvdata(pdev); + struct resource *res_emma; + int irq_emma; + int err = 0; + + res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 1); + irq_emma = platform_get_irq(pdev, 1); + if (!res_emma || !irq_emma) { + dev_err(pcdev->dev, "no EMMA resources\n"); + err = -ENODEV; + goto out; + } + + pcdev->base_emma = devm_ioremap_resource(pcdev->dev, res_emma); + if (IS_ERR(pcdev->base_emma)) { + err = PTR_ERR(pcdev->base_emma); + goto out; + } + + err = devm_request_irq(pcdev->dev, irq_emma, mx27_camera_emma_irq, 0, + MX2_CAM_DRV_NAME, pcdev); + if (err) { + dev_err(pcdev->dev, "Camera EMMA interrupt register failed\n"); + goto out; + } + + pcdev->clk_emma_ipg = devm_clk_get(pcdev->dev, "emma-ipg"); + if (IS_ERR(pcdev->clk_emma_ipg)) { + err = PTR_ERR(pcdev->clk_emma_ipg); + goto out; + } + + clk_prepare_enable(pcdev->clk_emma_ipg); + + pcdev->clk_emma_ahb = devm_clk_get(pcdev->dev, "emma-ahb"); + if (IS_ERR(pcdev->clk_emma_ahb)) { + err = PTR_ERR(pcdev->clk_emma_ahb); + goto exit_clk_emma_ipg; + } + + clk_prepare_enable(pcdev->clk_emma_ahb); + + err = mx27_camera_emma_prp_reset(pcdev); + if (err) + goto exit_clk_emma_ahb; + + return err; + +exit_clk_emma_ahb: + clk_disable_unprepare(pcdev->clk_emma_ahb); +exit_clk_emma_ipg: + clk_disable_unprepare(pcdev->clk_emma_ipg); +out: + return err; +} + +static int mx2_camera_probe(struct platform_device *pdev) +{ + struct mx2_camera_dev *pcdev; + struct resource *res_csi; + int irq_csi; + int err = 0; + + dev_dbg(&pdev->dev, "initialising\n"); + + res_csi = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq_csi = platform_get_irq(pdev, 0); + if (res_csi == NULL || irq_csi < 0) { + dev_err(&pdev->dev, "Missing platform resources data\n"); + err = -ENODEV; + goto exit; + } + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) { + dev_err(&pdev->dev, "Could not allocate pcdev\n"); + err = -ENOMEM; + goto exit; + } + + pcdev->clk_csi_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(pcdev->clk_csi_ahb)) { + dev_err(&pdev->dev, "Could not get csi ahb clock\n"); + err = PTR_ERR(pcdev->clk_csi_ahb); + goto exit; + } + + pcdev->clk_csi_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(pcdev->clk_csi_per)) { + dev_err(&pdev->dev, "Could not get csi per clock\n"); + err = PTR_ERR(pcdev->clk_csi_per); + goto exit; + } + + pcdev->pdata = pdev->dev.platform_data; + if (pcdev->pdata) { + long rate; + + pcdev->platform_flags = pcdev->pdata->flags; + + rate = clk_round_rate(pcdev->clk_csi_per, + pcdev->pdata->clk * 2); + if (rate <= 0) { + err = -ENODEV; + goto exit; + } + err = clk_set_rate(pcdev->clk_csi_per, rate); + if (err < 0) + goto exit; + } + + INIT_LIST_HEAD(&pcdev->capture); + INIT_LIST_HEAD(&pcdev->active_bufs); + INIT_LIST_HEAD(&pcdev->discard); + spin_lock_init(&pcdev->lock); + + pcdev->base_csi = devm_ioremap_resource(&pdev->dev, res_csi); + if (IS_ERR(pcdev->base_csi)) { + err = PTR_ERR(pcdev->base_csi); + goto exit; + } + + pcdev->dev = &pdev->dev; + platform_set_drvdata(pdev, pcdev); + + err = mx27_camera_emma_init(pdev); + if (err) + goto exit; + + /* + * We're done with drvdata here. Clear the pointer so that + * v4l2 core can start using drvdata on its purpose. + */ + platform_set_drvdata(pdev, NULL); + + pcdev->soc_host.drv_name = MX2_CAM_DRV_NAME, + pcdev->soc_host.ops = &mx2_soc_camera_host_ops, + pcdev->soc_host.priv = pcdev; + pcdev->soc_host.v4l2_dev.dev = &pdev->dev; + pcdev->soc_host.nr = pdev->id; + + pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(pcdev->alloc_ctx)) { + err = PTR_ERR(pcdev->alloc_ctx); + goto eallocctx; + } + err = soc_camera_host_register(&pcdev->soc_host); + if (err) + goto exit_free_emma; + + dev_info(&pdev->dev, "MX2 Camera (CSI) driver probed, clock frequency: %ld\n", + clk_get_rate(pcdev->clk_csi_per)); + + return 0; + +exit_free_emma: + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); +eallocctx: + clk_disable_unprepare(pcdev->clk_emma_ipg); + clk_disable_unprepare(pcdev->clk_emma_ahb); +exit: + return err; +} + +static int mx2_camera_remove(struct platform_device *pdev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); + struct mx2_camera_dev *pcdev = container_of(soc_host, + struct mx2_camera_dev, soc_host); + + soc_camera_host_unregister(&pcdev->soc_host); + + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); + + clk_disable_unprepare(pcdev->clk_emma_ipg); + clk_disable_unprepare(pcdev->clk_emma_ahb); + + dev_info(&pdev->dev, "MX2 Camera driver unloaded\n"); + + return 0; +} + +static struct platform_driver mx2_camera_driver = { + .driver = { + .name = MX2_CAM_DRV_NAME, + }, + .id_table = mx2_camera_devtype, + .remove = mx2_camera_remove, +}; + +module_platform_driver_probe(mx2_camera_driver, mx2_camera_probe); + +MODULE_DESCRIPTION("i.MX27 SoC Camera Host driver"); +MODULE_AUTHOR("Sascha Hauer "); +MODULE_LICENSE("GPL"); +MODULE_VERSION(MX2_CAM_VERSION); diff --git a/drivers/staging/media/mx3/Kconfig b/drivers/staging/media/mx3/Kconfig new file mode 100644 index 000000000..595d5fe7c --- /dev/null +++ b/drivers/staging/media/mx3/Kconfig @@ -0,0 +1,15 @@ +config VIDEO_MX3 + tristate "i.MX3x Camera Sensor Interface driver" + depends on VIDEO_DEV && MX3_IPU && SOC_CAMERA + depends on MX3_IPU || COMPILE_TEST + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + ---help--- + This is a v4l2 driver for the i.MX3x Camera Sensor Interface + + This driver is deprecated: it should become a stand-alone driver + instead of using the soc-camera framework. + + Unless someone is willing to take this on (unlikely with such + ancient hardware) it is going to be removed from the kernel + soon. diff --git a/drivers/staging/media/mx3/Makefile b/drivers/staging/media/mx3/Makefile new file mode 100644 index 000000000..6d91dcd80 --- /dev/null +++ b/drivers/staging/media/mx3/Makefile @@ -0,0 +1,3 @@ +# Makefile for i.MX3x Camera Sensor driver + +obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o diff --git a/drivers/staging/media/mx3/TODO b/drivers/staging/media/mx3/TODO new file mode 100644 index 000000000..bc68fa443 --- /dev/null +++ b/drivers/staging/media/mx3/TODO @@ -0,0 +1,10 @@ +This driver is deprecated: it should become a stand-alone driver instead of +using the soc-camera framework. + +Unless someone is willing to take this on (unlikely with such ancient +hardware) it is going to be removed from the kernel soon. + +Note that trivial patches will not be accepted anymore, only a full conversion. + +If you want to convert this driver, please contact the linux-media mailinglist +(see http://linuxtv.org/lists.php). diff --git a/drivers/staging/media/mx3/mx3_camera.c b/drivers/staging/media/mx3/mx3_camera.c new file mode 100644 index 000000000..aa39e9569 --- /dev/null +++ b/drivers/staging/media/mx3/mx3_camera.c @@ -0,0 +1,1264 @@ +/* + * V4L2 Driver for i.MX3x camera host + * + * Copyright (C) 2008 + * Guennadi Liakhovetski, DENX Software Engineering, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define MX3_CAM_DRV_NAME "mx3-camera" + +/* CMOS Sensor Interface Registers */ +#define CSI_REG_START 0x60 + +#define CSI_SENS_CONF (0x60 - CSI_REG_START) +#define CSI_SENS_FRM_SIZE (0x64 - CSI_REG_START) +#define CSI_ACT_FRM_SIZE (0x68 - CSI_REG_START) +#define CSI_OUT_FRM_CTRL (0x6C - CSI_REG_START) +#define CSI_TST_CTRL (0x70 - CSI_REG_START) +#define CSI_CCIR_CODE_1 (0x74 - CSI_REG_START) +#define CSI_CCIR_CODE_2 (0x78 - CSI_REG_START) +#define CSI_CCIR_CODE_3 (0x7C - CSI_REG_START) +#define CSI_FLASH_STROBE_1 (0x80 - CSI_REG_START) +#define CSI_FLASH_STROBE_2 (0x84 - CSI_REG_START) + +#define CSI_SENS_CONF_VSYNC_POL_SHIFT 0 +#define CSI_SENS_CONF_HSYNC_POL_SHIFT 1 +#define CSI_SENS_CONF_DATA_POL_SHIFT 2 +#define CSI_SENS_CONF_PIX_CLK_POL_SHIFT 3 +#define CSI_SENS_CONF_SENS_PRTCL_SHIFT 4 +#define CSI_SENS_CONF_SENS_CLKSRC_SHIFT 7 +#define CSI_SENS_CONF_DATA_FMT_SHIFT 8 +#define CSI_SENS_CONF_DATA_WIDTH_SHIFT 10 +#define CSI_SENS_CONF_EXT_VSYNC_SHIFT 15 +#define CSI_SENS_CONF_DIVRATIO_SHIFT 16 + +#define CSI_SENS_CONF_DATA_FMT_RGB_YUV444 (0UL << CSI_SENS_CONF_DATA_FMT_SHIFT) +#define CSI_SENS_CONF_DATA_FMT_YUV422 (2UL << CSI_SENS_CONF_DATA_FMT_SHIFT) +#define CSI_SENS_CONF_DATA_FMT_BAYER (3UL << CSI_SENS_CONF_DATA_FMT_SHIFT) + +#define MAX_VIDEO_MEM 16 + +struct mx3_camera_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head queue; + + /* One descriptot per scatterlist (per frame) */ + struct dma_async_tx_descriptor *txd; + + /* We have to "build" a scatterlist ourselves - one element per frame */ + struct scatterlist sg; +}; + +/** + * struct mx3_camera_dev - i.MX3x camera (CSI) object + * @dev: camera device, to which the coherent buffer is attached + * @icd: currently attached camera sensor + * @clk: pointer to clock + * @base: remapped register base address + * @pdata: platform data + * @platform_flags: platform flags + * @mclk: master clock frequency in Hz + * @capture: list of capture videobuffers + * @lock: protects video buffer lists + * @active: active video buffer + * @idmac_channel: array of pointers to IPU DMAC DMA channels + * @soc_host: embedded soc_host object + */ +struct mx3_camera_dev { + /* + * i.MX3x is only supposed to handle one camera on its Camera Sensor + * Interface. If anyone ever builds hardware to enable more than one + * camera _simultaneously_, they will have to modify this driver too + */ + struct clk *clk; + + void __iomem *base; + + struct mx3_camera_pdata *pdata; + + unsigned long platform_flags; + unsigned long mclk; + u16 width_flags; /* max 15 bits */ + + struct list_head capture; + spinlock_t lock; /* Protects video buffer lists */ + struct mx3_camera_buffer *active; + size_t buf_total; + struct vb2_alloc_ctx *alloc_ctx; + enum v4l2_field field; + int sequence; + + /* IDMAC / dmaengine interface */ + struct idmac_channel *idmac_channel[1]; /* We need one channel */ + + struct soc_camera_host soc_host; +}; + +struct dma_chan_request { + struct mx3_camera_dev *mx3_cam; + enum ipu_channel id; +}; + +static u32 csi_reg_read(struct mx3_camera_dev *mx3, off_t reg) +{ + return __raw_readl(mx3->base + reg); +} + +static void csi_reg_write(struct mx3_camera_dev *mx3, u32 value, off_t reg) +{ + __raw_writel(value, mx3->base + reg); +} + +static struct mx3_camera_buffer *to_mx3_vb(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct mx3_camera_buffer, vb); +} + +/* Called from the IPU IDMAC ISR */ +static void mx3_cam_dma_done(void *arg) +{ + struct idmac_tx_desc *desc = to_tx_desc(arg); + struct dma_chan *chan = desc->txd.chan; + struct idmac_channel *ichannel = to_idmac_chan(chan); + struct mx3_camera_dev *mx3_cam = ichannel->client; + + dev_dbg(chan->device->dev, "callback cookie %d, active DMA %pad\n", + desc->txd.cookie, mx3_cam->active ? &sg_dma_address(&mx3_cam->active->sg) : NULL); + + spin_lock(&mx3_cam->lock); + if (mx3_cam->active) { + struct vb2_v4l2_buffer *vb = &mx3_cam->active->vb; + struct mx3_camera_buffer *buf = to_mx3_vb(vb); + + list_del_init(&buf->queue); + vb->vb2_buf.timestamp = ktime_get_ns(); + vb->field = mx3_cam->field; + vb->sequence = mx3_cam->sequence++; + vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE); + } + + if (list_empty(&mx3_cam->capture)) { + mx3_cam->active = NULL; + spin_unlock(&mx3_cam->lock); + + /* + * stop capture - without further buffers IPU_CHA_BUF0_RDY will + * not get updated + */ + return; + } + + mx3_cam->active = list_entry(mx3_cam->capture.next, + struct mx3_camera_buffer, queue); + spin_unlock(&mx3_cam->lock); +} + +/* + * Videobuf operations + */ + +/* + * Calculate the __buffer__ (not data) size and number of buffers. + */ +static int mx3_videobuf_setup(struct vb2_queue *vq, + unsigned int *count, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + + if (!mx3_cam->idmac_channel[0]) + return -EINVAL; + + alloc_ctxs[0] = mx3_cam->alloc_ctx; + + if (!vq->num_buffers) + mx3_cam->sequence = 0; + + if (!*count) + *count = 2; + + /* Called from VIDIOC_REQBUFS or in compatibility mode */ + if (!*num_planes) + sizes[0] = icd->sizeimage; + else if (sizes[0] < icd->sizeimage) + return -EINVAL; + + /* If *num_planes != 0, we have already verified *count. */ + if (sizes[0] * *count + mx3_cam->buf_total > MAX_VIDEO_MEM * 1024 * 1024) + *count = (MAX_VIDEO_MEM * 1024 * 1024 - mx3_cam->buf_total) / + sizes[0]; + + *num_planes = 1; + + return 0; +} + +static enum pixel_fmt fourcc_to_ipu_pix(__u32 fourcc) +{ + /* Add more formats as need arises and test possibilities appear... */ + switch (fourcc) { + case V4L2_PIX_FMT_RGB24: + return IPU_PIX_FMT_RGB24; + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_RGB565: + default: + return IPU_PIX_FMT_GENERIC; + } +} + +static void mx3_videobuf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct mx3_camera_buffer *buf = to_mx3_vb(vbuf); + struct scatterlist *sg = &buf->sg; + struct dma_async_tx_descriptor *txd; + struct idmac_channel *ichan = mx3_cam->idmac_channel[0]; + struct idmac_video_param *video = &ichan->params.video; + const struct soc_mbus_pixelfmt *host_fmt = icd->current_fmt->host_fmt; + dma_cookie_t cookie; + size_t new_size; + + new_size = icd->sizeimage; + + if (vb2_plane_size(vb, 0) < new_size) { + dev_err(icd->parent, "Buffer #%d too small (%lu < %zu)\n", + vbuf->vb2_buf.index, vb2_plane_size(vb, 0), new_size); + goto error; + } + + if (!buf->txd) { + sg_dma_address(sg) = vb2_dma_contig_plane_dma_addr(vb, 0); + sg_dma_len(sg) = new_size; + + txd = dmaengine_prep_slave_sg( + &ichan->dma_chan, sg, 1, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!txd) + goto error; + + txd->callback_param = txd; + txd->callback = mx3_cam_dma_done; + + buf->txd = txd; + } else { + txd = buf->txd; + } + + vb2_set_plane_payload(vb, 0, new_size); + + /* This is the configuration of one sg-element */ + video->out_pixel_fmt = fourcc_to_ipu_pix(host_fmt->fourcc); + + if (video->out_pixel_fmt == IPU_PIX_FMT_GENERIC) { + /* + * If the IPU DMA channel is configured to transfer generic + * 8-bit data, we have to set up the geometry parameters + * correctly, according to the current pixel format. The DMA + * horizontal parameters in this case are expressed in bytes, + * not in pixels. + */ + video->out_width = icd->bytesperline; + video->out_height = icd->user_height; + video->out_stride = icd->bytesperline; + } else { + /* + * For IPU known formats the pixel unit will be managed + * successfully by the IPU code + */ + video->out_width = icd->user_width; + video->out_height = icd->user_height; + video->out_stride = icd->user_width; + } + +#ifdef DEBUG + /* helps to see what DMA actually has written */ + if (vb2_plane_vaddr(vb, 0)) + memset(vb2_plane_vaddr(vb, 0), 0xaa, vb2_get_plane_payload(vb, 0)); +#endif + + spin_lock_irq(&mx3_cam->lock); + list_add_tail(&buf->queue, &mx3_cam->capture); + + if (!mx3_cam->active) + mx3_cam->active = buf; + + spin_unlock_irq(&mx3_cam->lock); + + cookie = txd->tx_submit(txd); + dev_dbg(icd->parent, "Submitted cookie %d DMA %pad\n", + cookie, &sg_dma_address(&buf->sg)); + + if (cookie >= 0) + return; + + spin_lock_irq(&mx3_cam->lock); + + /* Submit error */ + list_del_init(&buf->queue); + + if (mx3_cam->active == buf) + mx3_cam->active = NULL; + + spin_unlock_irq(&mx3_cam->lock); +error: + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +static void mx3_videobuf_release(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct mx3_camera_buffer *buf = to_mx3_vb(vbuf); + struct dma_async_tx_descriptor *txd = buf->txd; + unsigned long flags; + + dev_dbg(icd->parent, + "Release%s DMA %pad, queue %sempty\n", + mx3_cam->active == buf ? " active" : "", &sg_dma_address(&buf->sg), + list_empty(&buf->queue) ? "" : "not "); + + spin_lock_irqsave(&mx3_cam->lock, flags); + + if (mx3_cam->active == buf) + mx3_cam->active = NULL; + + /* Doesn't hurt also if the list is empty */ + list_del_init(&buf->queue); + + if (txd) { + buf->txd = NULL; + if (mx3_cam->idmac_channel[0]) + async_tx_ack(txd); + } + + spin_unlock_irqrestore(&mx3_cam->lock, flags); + + mx3_cam->buf_total -= vb2_plane_size(vb, 0); +} + +static int mx3_videobuf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct mx3_camera_buffer *buf = to_mx3_vb(vbuf); + + if (!buf->txd) { + /* This is for locking debugging only */ + INIT_LIST_HEAD(&buf->queue); + sg_init_table(&buf->sg, 1); + + mx3_cam->buf_total += vb2_plane_size(vb, 0); + } + + return 0; +} + +static void mx3_stop_streaming(struct vb2_queue *q) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(q); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct idmac_channel *ichan = mx3_cam->idmac_channel[0]; + struct mx3_camera_buffer *buf, *tmp; + unsigned long flags; + + if (ichan) + dmaengine_pause(&ichan->dma_chan); + + spin_lock_irqsave(&mx3_cam->lock, flags); + + mx3_cam->active = NULL; + + list_for_each_entry_safe(buf, tmp, &mx3_cam->capture, queue) { + list_del_init(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irqrestore(&mx3_cam->lock, flags); +} + +static struct vb2_ops mx3_videobuf_ops = { + .queue_setup = mx3_videobuf_setup, + .buf_queue = mx3_videobuf_queue, + .buf_cleanup = mx3_videobuf_release, + .buf_init = mx3_videobuf_init, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = mx3_stop_streaming, +}; + +static int mx3_camera_init_videobuf(struct vb2_queue *q, + struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = icd; + q->ops = &mx3_videobuf_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct mx3_camera_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &ici->host_lock; + + return vb2_queue_init(q); +} + +/* First part of ipu_csi_init_interface() */ +static void mx3_camera_activate(struct mx3_camera_dev *mx3_cam) +{ + u32 conf; + long rate; + + /* Set default size: ipu_csi_set_window_size() */ + csi_reg_write(mx3_cam, (640 - 1) | ((480 - 1) << 16), CSI_ACT_FRM_SIZE); + /* ...and position to 0:0: ipu_csi_set_window_pos() */ + conf = csi_reg_read(mx3_cam, CSI_OUT_FRM_CTRL) & 0xffff0000; + csi_reg_write(mx3_cam, conf, CSI_OUT_FRM_CTRL); + + /* We use only gated clock synchronisation mode so far */ + conf = 0 << CSI_SENS_CONF_SENS_PRTCL_SHIFT; + + /* Set generic data, platform-biggest bus-width */ + conf |= CSI_SENS_CONF_DATA_FMT_BAYER; + + if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15) + conf |= 3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + else if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_10) + conf |= 2 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + else if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_8) + conf |= 1 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + else/* if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_4)*/ + conf |= 0 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + + if (mx3_cam->platform_flags & MX3_CAMERA_CLK_SRC) + conf |= 1 << CSI_SENS_CONF_SENS_CLKSRC_SHIFT; + if (mx3_cam->platform_flags & MX3_CAMERA_EXT_VSYNC) + conf |= 1 << CSI_SENS_CONF_EXT_VSYNC_SHIFT; + if (mx3_cam->platform_flags & MX3_CAMERA_DP) + conf |= 1 << CSI_SENS_CONF_DATA_POL_SHIFT; + if (mx3_cam->platform_flags & MX3_CAMERA_PCP) + conf |= 1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT; + if (mx3_cam->platform_flags & MX3_CAMERA_HSP) + conf |= 1 << CSI_SENS_CONF_HSYNC_POL_SHIFT; + if (mx3_cam->platform_flags & MX3_CAMERA_VSP) + conf |= 1 << CSI_SENS_CONF_VSYNC_POL_SHIFT; + + /* ipu_csi_init_interface() */ + csi_reg_write(mx3_cam, conf, CSI_SENS_CONF); + + clk_prepare_enable(mx3_cam->clk); + rate = clk_round_rate(mx3_cam->clk, mx3_cam->mclk); + dev_dbg(mx3_cam->soc_host.v4l2_dev.dev, "Set SENS_CONF to %x, rate %ld\n", conf, rate); + if (rate) + clk_set_rate(mx3_cam->clk, rate); +} + +static int mx3_camera_add_device(struct soc_camera_device *icd) +{ + dev_info(icd->parent, "MX3 Camera driver attached to camera %d\n", + icd->devnum); + + return 0; +} + +static void mx3_camera_remove_device(struct soc_camera_device *icd) +{ + dev_info(icd->parent, "MX3 Camera driver detached from camera %d\n", + icd->devnum); +} + +/* Called with .host_lock held */ +static int mx3_camera_clock_start(struct soc_camera_host *ici) +{ + struct mx3_camera_dev *mx3_cam = ici->priv; + + mx3_camera_activate(mx3_cam); + + mx3_cam->buf_total = 0; + + return 0; +} + +/* Called with .host_lock held */ +static void mx3_camera_clock_stop(struct soc_camera_host *ici) +{ + struct mx3_camera_dev *mx3_cam = ici->priv; + struct idmac_channel **ichan = &mx3_cam->idmac_channel[0]; + + if (*ichan) { + dma_release_channel(&(*ichan)->dma_chan); + *ichan = NULL; + } + + clk_disable_unprepare(mx3_cam->clk); +} + +static int test_platform_param(struct mx3_camera_dev *mx3_cam, + unsigned char buswidth, unsigned long *flags) +{ + /* + * If requested data width is supported by the platform, use it or any + * possible lower value - i.MX31 is smart enough to shift bits + */ + if (buswidth > fls(mx3_cam->width_flags)) + return -EINVAL; + + /* + * Platform specified synchronization and pixel clock polarities are + * only a recommendation and are only used during probing. MX3x + * camera interface only works in master mode, i.e., uses HSYNC and + * VSYNC signals from the sensor + */ + *flags = V4L2_MBUS_MASTER | + V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_HSYNC_ACTIVE_LOW | + V4L2_MBUS_VSYNC_ACTIVE_HIGH | + V4L2_MBUS_VSYNC_ACTIVE_LOW | + V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_PCLK_SAMPLE_FALLING | + V4L2_MBUS_DATA_ACTIVE_HIGH | + V4L2_MBUS_DATA_ACTIVE_LOW; + + return 0; +} + +static int mx3_camera_try_bus_param(struct soc_camera_device *icd, + const unsigned int depth) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; + unsigned long bus_flags, common_flags; + int ret = test_platform_param(mx3_cam, depth, &bus_flags); + + dev_dbg(icd->parent, "request bus width %d bit: %d\n", depth, ret); + + if (ret < 0) + return ret; + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (!ret) { + common_flags = soc_mbus_config_compatible(&cfg, + bus_flags); + if (!common_flags) { + dev_warn(icd->parent, + "Flags incompatible: camera 0x%x, host 0x%lx\n", + cfg.flags, bus_flags); + return -EINVAL; + } + } else if (ret != -ENOIOCTLCMD) { + return ret; + } + + return 0; +} + +static bool chan_filter(struct dma_chan *chan, void *arg) +{ + struct dma_chan_request *rq = arg; + struct mx3_camera_pdata *pdata; + + if (!imx_dma_is_ipu(chan)) + return false; + + if (!rq) + return false; + + pdata = rq->mx3_cam->soc_host.v4l2_dev.dev->platform_data; + + return rq->id == chan->chan_id && + pdata->dma_dev == chan->device->dev; +} + +static const struct soc_mbus_pixelfmt mx3_camera_formats[] = { + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .name = "Bayer BGGR (sRGB) 8 bit", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .name = "Monochrome 8 bit", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}; + +/* This will be corrected as we get more formats */ +static bool mx3_camera_packing_supported(const struct soc_mbus_pixelfmt *fmt) +{ + return fmt->packing == SOC_MBUS_PACKING_NONE || + (fmt->bits_per_sample == 8 && + fmt->packing == SOC_MBUS_PACKING_2X8_PADHI) || + (fmt->bits_per_sample > 8 && + fmt->packing == SOC_MBUS_PACKING_EXTEND16); +} + +static int mx3_camera_get_formats(struct soc_camera_device *icd, unsigned int idx, + struct soc_camera_format_xlate *xlate) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + int formats = 0, ret; + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .index = idx, + }; + const struct soc_mbus_pixelfmt *fmt; + + ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code); + if (ret < 0) + /* No more formats */ + return 0; + + fmt = soc_mbus_get_fmtdesc(code.code); + if (!fmt) { + dev_warn(icd->parent, + "Unsupported format code #%u: 0x%x\n", idx, code.code); + return 0; + } + + /* This also checks support for the requested bits-per-sample */ + ret = mx3_camera_try_bus_param(icd, fmt->bits_per_sample); + if (ret < 0) + return 0; + + switch (code.code) { + case MEDIA_BUS_FMT_SBGGR10_1X10: + formats++; + if (xlate) { + xlate->host_fmt = &mx3_camera_formats[0]; + xlate->code = code.code; + xlate++; + dev_dbg(dev, "Providing format %s using code 0x%x\n", + mx3_camera_formats[0].name, code.code); + } + break; + case MEDIA_BUS_FMT_Y10_1X10: + formats++; + if (xlate) { + xlate->host_fmt = &mx3_camera_formats[1]; + xlate->code = code.code; + xlate++; + dev_dbg(dev, "Providing format %s using code 0x%x\n", + mx3_camera_formats[1].name, code.code); + } + break; + default: + if (!mx3_camera_packing_supported(fmt)) + return 0; + } + + /* Generic pass-through */ + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code.code; + dev_dbg(dev, "Providing format %c%c%c%c in pass-through mode\n", + (fmt->fourcc >> (0*8)) & 0xFF, + (fmt->fourcc >> (1*8)) & 0xFF, + (fmt->fourcc >> (2*8)) & 0xFF, + (fmt->fourcc >> (3*8)) & 0xFF); + xlate++; + } + + return formats; +} + +static void configure_geometry(struct mx3_camera_dev *mx3_cam, + unsigned int width, unsigned int height, + const struct soc_mbus_pixelfmt *fmt) +{ + u32 ctrl, width_field, height_field; + + if (fourcc_to_ipu_pix(fmt->fourcc) == IPU_PIX_FMT_GENERIC) { + /* + * As the CSI will be configured to output BAYER, here + * the width parameter count the number of samples to + * capture to complete the whole image width. + */ + unsigned int num, den; + int ret = soc_mbus_samples_per_pixel(fmt, &num, &den); + BUG_ON(ret < 0); + width = width * num / den; + } + + /* Setup frame size - this cannot be changed on-the-fly... */ + width_field = width - 1; + height_field = height - 1; + csi_reg_write(mx3_cam, width_field | (height_field << 16), CSI_SENS_FRM_SIZE); + + csi_reg_write(mx3_cam, width_field << 16, CSI_FLASH_STROBE_1); + csi_reg_write(mx3_cam, (height_field << 16) | 0x22, CSI_FLASH_STROBE_2); + + csi_reg_write(mx3_cam, width_field | (height_field << 16), CSI_ACT_FRM_SIZE); + + /* ...and position */ + ctrl = csi_reg_read(mx3_cam, CSI_OUT_FRM_CTRL) & 0xffff0000; + /* Sensor does the cropping */ + csi_reg_write(mx3_cam, ctrl | 0 | (0 << 8), CSI_OUT_FRM_CTRL); +} + +static int acquire_dma_channel(struct mx3_camera_dev *mx3_cam) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + struct idmac_channel **ichan = &mx3_cam->idmac_channel[0]; + /* We have to use IDMAC_IC_7 for Bayer / generic data */ + struct dma_chan_request rq = {.mx3_cam = mx3_cam, + .id = IDMAC_IC_7}; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + chan = dma_request_channel(mask, chan_filter, &rq); + if (!chan) + return -EBUSY; + + *ichan = to_idmac_chan(chan); + (*ichan)->client = mx3_cam; + + return 0; +} + +/* + * FIXME: learn to use stride != width, then we can keep stride properly aligned + * and support arbitrary (even) widths. + */ +static inline void stride_align(__u32 *width) +{ + if (ALIGN(*width, 8) < 4096) + *width = ALIGN(*width, 8); + else + *width = *width & ~7; +} + +/* + * As long as we don't implement host-side cropping and scaling, we can use + * default g_crop and cropcap from soc_camera.c + */ +static int mx3_camera_set_crop(struct soc_camera_device *icd, + const struct v4l2_crop *a) +{ + struct v4l2_crop a_writable = *a; + struct v4l2_rect *rect = &a_writable.c; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &fmt.format; + int ret; + + soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096); + soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096); + + ret = v4l2_subdev_call(sd, video, s_crop, a); + if (ret < 0) + return ret; + + /* The capture device might have changed its output sizes */ + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret < 0) + return ret; + + if (mf->code != icd->current_fmt->code) + return -EINVAL; + + if (mf->width & 7) { + /* Ouch! We can only handle 8-byte aligned width... */ + stride_align(&mf->width); + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &fmt); + if (ret < 0) + return ret; + } + + if (mf->width != icd->user_width || mf->height != icd->user_height) + configure_geometry(mx3_cam, mf->width, mf->height, + icd->current_fmt->host_fmt); + + dev_dbg(icd->parent, "Sensor cropped %dx%d\n", + mf->width, mf->height); + + icd->user_width = mf->width; + icd->user_height = mf->height; + + return ret; +} + +static int mx3_camera_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); + if (!xlate) { + dev_warn(icd->parent, "Format %x not found\n", + pix->pixelformat); + return -EINVAL; + } + + stride_align(&pix->width); + dev_dbg(icd->parent, "Set format %dx%d\n", pix->width, pix->height); + + /* + * Might have to perform a complete interface initialisation like in + * ipu_csi_init_interface() in mxc_v4l2_s_param(). Also consider + * mxc_v4l2_s_fmt() + */ + + configure_geometry(mx3_cam, pix->width, pix->height, xlate->host_fmt); + + mf->width = pix->width; + mf->height = pix->height; + mf->field = pix->field; + mf->colorspace = pix->colorspace; + mf->code = xlate->code; + + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &format); + if (ret < 0) + return ret; + + if (mf->code != xlate->code) + return -EINVAL; + + if (!mx3_cam->idmac_channel[0]) { + ret = acquire_dma_channel(mx3_cam); + if (ret < 0) + return ret; + } + + pix->width = mf->width; + pix->height = mf->height; + pix->field = mf->field; + mx3_cam->field = mf->field; + pix->colorspace = mf->colorspace; + icd->current_fmt = xlate; + + dev_dbg(icd->parent, "Sensor set %dx%d\n", pix->width, pix->height); + + return ret; +} + +static int mx3_camera_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + __u32 pixfmt = pix->pixelformat; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (pixfmt && !xlate) { + dev_warn(icd->parent, "Format %x not found\n", pixfmt); + return -EINVAL; + } + + /* limit to MX3 hardware capabilities */ + if (pix->height > 4096) + pix->height = 4096; + if (pix->width > 4096) + pix->width = 4096; + + /* limit to sensor capabilities */ + mf->width = pix->width; + mf->height = pix->height; + mf->field = pix->field; + mf->colorspace = pix->colorspace; + mf->code = xlate->code; + + ret = v4l2_subdev_call(sd, pad, set_fmt, &pad_cfg, &format); + if (ret < 0) + return ret; + + pix->width = mf->width; + pix->height = mf->height; + pix->colorspace = mf->colorspace; + + switch (mf->field) { + case V4L2_FIELD_ANY: + pix->field = V4L2_FIELD_NONE; + break; + case V4L2_FIELD_NONE: + break; + default: + dev_err(icd->parent, "Field type %d unsupported.\n", + mf->field); + ret = -EINVAL; + } + + return ret; +} + +static int mx3_camera_reqbufs(struct soc_camera_device *icd, + struct v4l2_requestbuffers *p) +{ + return 0; +} + +static unsigned int mx3_camera_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_device *icd = file->private_data; + + return vb2_poll(&icd->vb2_vidq, file, pt); +} + +static int mx3_camera_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + /* cap->name is set by the firendly caller:-> */ + strlcpy(cap->card, "i.MX3x Camera", sizeof(cap->card)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mx3_camera_set_bus_param(struct soc_camera_device *icd) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx3_camera_dev *mx3_cam = ici->priv; + struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; + u32 pixfmt = icd->current_fmt->host_fmt->fourcc; + unsigned long bus_flags, common_flags; + u32 dw, sens_conf; + const struct soc_mbus_pixelfmt *fmt; + int buswidth; + int ret; + const struct soc_camera_format_xlate *xlate; + struct device *dev = icd->parent; + + fmt = soc_mbus_get_fmtdesc(icd->current_fmt->code); + if (!fmt) + return -EINVAL; + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (!xlate) { + dev_warn(dev, "Format %x not found\n", pixfmt); + return -EINVAL; + } + + buswidth = fmt->bits_per_sample; + ret = test_platform_param(mx3_cam, buswidth, &bus_flags); + + dev_dbg(dev, "requested bus width %d bit: %d\n", buswidth, ret); + + if (ret < 0) + return ret; + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (!ret) { + common_flags = soc_mbus_config_compatible(&cfg, + bus_flags); + if (!common_flags) { + dev_warn(icd->parent, + "Flags incompatible: camera 0x%x, host 0x%lx\n", + cfg.flags, bus_flags); + return -EINVAL; + } + } else if (ret != -ENOIOCTLCMD) { + return ret; + } else { + common_flags = bus_flags; + } + + dev_dbg(dev, "Flags cam: 0x%x host: 0x%lx common: 0x%lx\n", + cfg.flags, bus_flags, common_flags); + + /* Make choices, based on platform preferences */ + if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) { + if (mx3_cam->platform_flags & MX3_CAMERA_HSP) + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW; + } + + if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) { + if (mx3_cam->platform_flags & MX3_CAMERA_VSP) + common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW; + } + + if ((common_flags & V4L2_MBUS_DATA_ACTIVE_HIGH) && + (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW)) { + if (mx3_cam->platform_flags & MX3_CAMERA_DP) + common_flags &= ~V4L2_MBUS_DATA_ACTIVE_HIGH; + else + common_flags &= ~V4L2_MBUS_DATA_ACTIVE_LOW; + } + + if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) && + (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) { + if (mx3_cam->platform_flags & MX3_CAMERA_PCP) + common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING; + else + common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING; + } + + cfg.flags = common_flags; + ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(dev, "camera s_mbus_config(0x%lx) returned %d\n", + common_flags, ret); + return ret; + } + + /* + * So far only gated clock mode is supported. Add a line + * (3 << CSI_SENS_CONF_SENS_PRTCL_SHIFT) | + * below and select the required mode when supporting other + * synchronisation protocols. + */ + sens_conf = csi_reg_read(mx3_cam, CSI_SENS_CONF) & + ~((1 << CSI_SENS_CONF_VSYNC_POL_SHIFT) | + (1 << CSI_SENS_CONF_HSYNC_POL_SHIFT) | + (1 << CSI_SENS_CONF_DATA_POL_SHIFT) | + (1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT) | + (3 << CSI_SENS_CONF_DATA_FMT_SHIFT) | + (3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT)); + + /* TODO: Support RGB and YUV formats */ + + /* This has been set in mx3_camera_activate(), but we clear it above */ + sens_conf |= CSI_SENS_CONF_DATA_FMT_BAYER; + + if (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + sens_conf |= 1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT; + if (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + sens_conf |= 1 << CSI_SENS_CONF_HSYNC_POL_SHIFT; + if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + sens_conf |= 1 << CSI_SENS_CONF_VSYNC_POL_SHIFT; + if (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW) + sens_conf |= 1 << CSI_SENS_CONF_DATA_POL_SHIFT; + + /* Just do what we're asked to do */ + switch (xlate->host_fmt->bits_per_sample) { + case 4: + dw = 0 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + break; + case 8: + dw = 1 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + break; + case 10: + dw = 2 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + break; + default: + /* + * Actually it can only be 15 now, default is just to silence + * compiler warnings + */ + case 15: + dw = 3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; + } + + csi_reg_write(mx3_cam, sens_conf | dw, CSI_SENS_CONF); + + dev_dbg(dev, "Set SENS_CONF to %x\n", sens_conf | dw); + + return 0; +} + +static struct soc_camera_host_ops mx3_soc_camera_host_ops = { + .owner = THIS_MODULE, + .add = mx3_camera_add_device, + .remove = mx3_camera_remove_device, + .clock_start = mx3_camera_clock_start, + .clock_stop = mx3_camera_clock_stop, + .set_crop = mx3_camera_set_crop, + .set_fmt = mx3_camera_set_fmt, + .try_fmt = mx3_camera_try_fmt, + .get_formats = mx3_camera_get_formats, + .init_videobuf2 = mx3_camera_init_videobuf, + .reqbufs = mx3_camera_reqbufs, + .poll = mx3_camera_poll, + .querycap = mx3_camera_querycap, + .set_bus_param = mx3_camera_set_bus_param, +}; + +static int mx3_camera_probe(struct platform_device *pdev) +{ + struct mx3_camera_pdata *pdata = pdev->dev.platform_data; + struct mx3_camera_dev *mx3_cam; + struct resource *res; + void __iomem *base; + int err = 0; + struct soc_camera_host *soc_host; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + if (!pdata) + return -EINVAL; + + mx3_cam = devm_kzalloc(&pdev->dev, sizeof(*mx3_cam), GFP_KERNEL); + if (!mx3_cam) { + dev_err(&pdev->dev, "Could not allocate mx3 camera object\n"); + return -ENOMEM; + } + + mx3_cam->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mx3_cam->clk)) + return PTR_ERR(mx3_cam->clk); + + mx3_cam->pdata = pdata; + mx3_cam->platform_flags = pdata->flags; + if (!(mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_MASK)) { + /* + * Platform hasn't set available data widths. This is bad. + * Warn and use a default. + */ + dev_warn(&pdev->dev, "WARNING! Platform hasn't set available " + "data widths, using default 8 bit\n"); + mx3_cam->platform_flags |= MX3_CAMERA_DATAWIDTH_8; + } + if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_4) + mx3_cam->width_flags = 1 << 3; + if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_8) + mx3_cam->width_flags |= 1 << 7; + if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_10) + mx3_cam->width_flags |= 1 << 9; + if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15) + mx3_cam->width_flags |= 1 << 14; + + mx3_cam->mclk = pdata->mclk_10khz * 10000; + if (!mx3_cam->mclk) { + dev_warn(&pdev->dev, + "mclk_10khz == 0! Please, fix your platform data. " + "Using default 20MHz\n"); + mx3_cam->mclk = 20000000; + } + + /* list of video-buffers */ + INIT_LIST_HEAD(&mx3_cam->capture); + spin_lock_init(&mx3_cam->lock); + + mx3_cam->base = base; + + soc_host = &mx3_cam->soc_host; + soc_host->drv_name = MX3_CAM_DRV_NAME; + soc_host->ops = &mx3_soc_camera_host_ops; + soc_host->priv = mx3_cam; + soc_host->v4l2_dev.dev = &pdev->dev; + soc_host->nr = pdev->id; + + mx3_cam->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(mx3_cam->alloc_ctx)) + return PTR_ERR(mx3_cam->alloc_ctx); + + if (pdata->asd_sizes) { + soc_host->asd = pdata->asd; + soc_host->asd_sizes = pdata->asd_sizes; + } + + err = soc_camera_host_register(soc_host); + if (err) + goto ecamhostreg; + + /* IDMAC interface */ + dmaengine_get(); + + return 0; + +ecamhostreg: + vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx); + return err; +} + +static int mx3_camera_remove(struct platform_device *pdev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); + struct mx3_camera_dev *mx3_cam = container_of(soc_host, + struct mx3_camera_dev, soc_host); + + soc_camera_host_unregister(soc_host); + + /* + * The channel has either not been allocated, + * or should have been released + */ + if (WARN_ON(mx3_cam->idmac_channel[0])) + dma_release_channel(&mx3_cam->idmac_channel[0]->dma_chan); + + vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx); + + dmaengine_put(); + + return 0; +} + +static struct platform_driver mx3_camera_driver = { + .driver = { + .name = MX3_CAM_DRV_NAME, + }, + .probe = mx3_camera_probe, + .remove = mx3_camera_remove, +}; + +module_platform_driver(mx3_camera_driver); + +MODULE_DESCRIPTION("i.MX3x SoC Camera Host driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.2.3"); +MODULE_ALIAS("platform:" MX3_CAM_DRV_NAME); diff --git a/drivers/staging/media/omap1/Kconfig b/drivers/staging/media/omap1/Kconfig new file mode 100644 index 000000000..6cfab3a04 --- /dev/null +++ b/drivers/staging/media/omap1/Kconfig @@ -0,0 +1,13 @@ +config VIDEO_OMAP1 + tristate "OMAP1 Camera Interface driver" + depends on VIDEO_DEV && SOC_CAMERA + depends on ARCH_OMAP1 + depends on HAS_DMA + select VIDEOBUF_DMA_CONTIG + select VIDEOBUF_DMA_SG + ---help--- + This is a v4l2 driver for the TI OMAP1 camera interface + + This driver is deprecated and will be removed soon unless someone + will start the work to convert this driver to the vb2 framework + and remove the soc-camera dependency. diff --git a/drivers/staging/media/omap1/Makefile b/drivers/staging/media/omap1/Makefile new file mode 100644 index 000000000..288562260 --- /dev/null +++ b/drivers/staging/media/omap1/Makefile @@ -0,0 +1,3 @@ +# Makefile for OMAP1 driver + +obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o diff --git a/drivers/staging/media/omap1/TODO b/drivers/staging/media/omap1/TODO new file mode 100644 index 000000000..1025f9f60 --- /dev/null +++ b/drivers/staging/media/omap1/TODO @@ -0,0 +1,8 @@ +This driver is deprecated and will be removed soon unless someone will start +the work to convert this driver to the vb2 framework and remove the +soc-camera dependency. + +Note that trivial patches will not be accepted anymore, only a full conversion. + +If you want to convert this driver, please contact the linux-media mailinglist +(see http://linuxtv.org/lists.php). diff --git a/drivers/staging/media/omap1/omap1_camera.c b/drivers/staging/media/omap1/omap1_camera.c new file mode 100644 index 000000000..bd721e354 --- /dev/null +++ b/drivers/staging/media/omap1/omap1_camera.c @@ -0,0 +1,1738 @@ +/* + * V4L2 SoC Camera driver for OMAP1 Camera Interface + * + * Copyright (C) 2010, Janusz Krzysztofik + * + * Based on V4L2 Driver for i.MXL/i.MXL camera (CSI) host + * Copyright (C) 2008, Paulius Zaleckas + * Copyright (C) 2009, Darius Augulis + * + * Based on PXA SoC camera driver + * Copyright (C) 2006, Sascha Hauer, Pengutronix + * Copyright (C) 2008, Guennadi Liakhovetski + * + * Hardware specific bits initialy based on former work by Matt Callow + * drivers/media/platform/omap/omap1510cam.c + * Copyright (C) 2006 Matt Callow + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + +#define DRIVER_NAME "omap1-camera" +#define DRIVER_VERSION "0.0.2" + +#define OMAP_DMA_CAMERA_IF_RX 20 + +/* + * --------------------------------------------------------------------------- + * OMAP1 Camera Interface registers + * --------------------------------------------------------------------------- + */ + +#define REG_CTRLCLOCK 0x00 +#define REG_IT_STATUS 0x04 +#define REG_MODE 0x08 +#define REG_STATUS 0x0C +#define REG_CAMDATA 0x10 +#define REG_GPIO 0x14 +#define REG_PEAK_COUNTER 0x18 + +/* CTRLCLOCK bit shifts */ +#define LCLK_EN BIT(7) +#define DPLL_EN BIT(6) +#define MCLK_EN BIT(5) +#define CAMEXCLK_EN BIT(4) +#define POLCLK BIT(3) +#define FOSCMOD_SHIFT 0 +#define FOSCMOD_MASK (0x7 << FOSCMOD_SHIFT) +#define FOSCMOD_12MHz 0x0 +#define FOSCMOD_6MHz 0x2 +#define FOSCMOD_9_6MHz 0x4 +#define FOSCMOD_24MHz 0x5 +#define FOSCMOD_8MHz 0x6 + +/* IT_STATUS bit shifts */ +#define DATA_TRANSFER BIT(5) +#define FIFO_FULL BIT(4) +#define H_DOWN BIT(3) +#define H_UP BIT(2) +#define V_DOWN BIT(1) +#define V_UP BIT(0) + +/* MODE bit shifts */ +#define RAZ_FIFO BIT(18) +#define EN_FIFO_FULL BIT(17) +#define EN_NIRQ BIT(16) +#define THRESHOLD_SHIFT 9 +#define THRESHOLD_MASK (0x7f << THRESHOLD_SHIFT) +#define DMA BIT(8) +#define EN_H_DOWN BIT(7) +#define EN_H_UP BIT(6) +#define EN_V_DOWN BIT(5) +#define EN_V_UP BIT(4) +#define ORDERCAMD BIT(3) + +#define IRQ_MASK (EN_V_UP | EN_V_DOWN | EN_H_UP | EN_H_DOWN | \ + EN_NIRQ | EN_FIFO_FULL) + +/* STATUS bit shifts */ +#define HSTATUS BIT(1) +#define VSTATUS BIT(0) + +/* GPIO bit shifts */ +#define CAM_RST BIT(0) + +/* end of OMAP1 Camera Interface registers */ + + +#define SOCAM_BUS_FLAGS (V4L2_MBUS_MASTER | \ + V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | \ + V4L2_MBUS_DATA_ACTIVE_HIGH) + + +#define FIFO_SIZE ((THRESHOLD_MASK >> THRESHOLD_SHIFT) + 1) +#define FIFO_SHIFT __fls(FIFO_SIZE) + +#define DMA_BURST_SHIFT (1 + OMAP_DMA_DATA_BURST_4) +#define DMA_BURST_SIZE (1 << DMA_BURST_SHIFT) + +#define DMA_ELEMENT_SHIFT OMAP_DMA_DATA_TYPE_S32 +#define DMA_ELEMENT_SIZE (1 << DMA_ELEMENT_SHIFT) + +#define DMA_FRAME_SHIFT_CONTIG (FIFO_SHIFT - 1) +#define DMA_FRAME_SHIFT_SG DMA_BURST_SHIFT + +#define DMA_FRAME_SHIFT(x) ((x) == OMAP1_CAM_DMA_CONTIG ? \ + DMA_FRAME_SHIFT_CONTIG : \ + DMA_FRAME_SHIFT_SG) +#define DMA_FRAME_SIZE(x) (1 << DMA_FRAME_SHIFT(x)) +#define DMA_SYNC OMAP_DMA_SYNC_FRAME +#define THRESHOLD_LEVEL DMA_FRAME_SIZE + + +#define MAX_VIDEO_MEM 4 /* arbitrary video memory limit in MB */ + + +/* + * Structures + */ + +/* buffer for one video frame */ +struct omap1_cam_buf { + struct videobuf_buffer vb; + u32 code; + int inwork; + struct scatterlist *sgbuf; + int sgcount; + int bytes_left; + enum videobuf_state result; +}; + +struct omap1_cam_dev { + struct soc_camera_host soc_host; + struct clk *clk; + + unsigned int irq; + void __iomem *base; + + int dma_ch; + + struct omap1_cam_platform_data *pdata; + struct resource *res; + unsigned long pflags; + unsigned long camexclk; + + struct list_head capture; + + /* lock used to protect videobuf */ + spinlock_t lock; + + /* Pointers to DMA buffers */ + struct omap1_cam_buf *active; + struct omap1_cam_buf *ready; + + enum omap1_cam_vb_mode vb_mode; + int (*mmap_mapper)(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma); + + u32 reg_cache[0]; +}; + + +static void cam_write(struct omap1_cam_dev *pcdev, u16 reg, u32 val) +{ + pcdev->reg_cache[reg / sizeof(u32)] = val; + __raw_writel(val, pcdev->base + reg); +} + +static u32 cam_read(struct omap1_cam_dev *pcdev, u16 reg, bool from_cache) +{ + return !from_cache ? __raw_readl(pcdev->base + reg) : + pcdev->reg_cache[reg / sizeof(u32)]; +} + +#define CAM_READ(pcdev, reg) \ + cam_read(pcdev, REG_##reg, false) +#define CAM_WRITE(pcdev, reg, val) \ + cam_write(pcdev, REG_##reg, val) +#define CAM_READ_CACHE(pcdev, reg) \ + cam_read(pcdev, REG_##reg, true) + +/* + * Videobuf operations + */ +static int omap1_videobuf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct omap1_cam_dev *pcdev = ici->priv; + + *size = icd->sizeimage; + + if (!*count || *count < OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode)) + *count = OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode); + + if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024) + *count = (MAX_VIDEO_MEM * 1024 * 1024) / *size; + + dev_dbg(icd->parent, + "%s: count=%d, size=%d\n", __func__, *count, *size); + + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct omap1_cam_buf *buf, + enum omap1_cam_vb_mode vb_mode) +{ + struct videobuf_buffer *vb = &buf->vb; + + BUG_ON(in_interrupt()); + + videobuf_waiton(vq, vb, 0, 0); + + if (vb_mode == OMAP1_CAM_DMA_CONTIG) { + videobuf_dma_contig_free(vq, vb); + } else { + struct soc_camera_device *icd = vq->priv_data; + struct device *dev = icd->parent; + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + videobuf_dma_unmap(dev, dma); + videobuf_dma_free(dma); + } + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int omap1_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct soc_camera_device *icd = vq->priv_data; + struct omap1_cam_buf *buf = container_of(vb, struct omap1_cam_buf, vb); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct omap1_cam_dev *pcdev = ici->priv; + int ret; + + WARN_ON(!list_empty(&vb->queue)); + + BUG_ON(NULL == icd->current_fmt); + + buf->inwork = 1; + + if (buf->code != icd->current_fmt->code || vb->field != field || + vb->width != icd->user_width || + vb->height != icd->user_height) { + buf->code = icd->current_fmt->code; + vb->width = icd->user_width; + vb->height = icd->user_height; + vb->field = field; + vb->state = VIDEOBUF_NEEDS_INIT; + } + + vb->size = icd->sizeimage; + + if (vb->baddr && vb->bsize < vb->size) { + ret = -EINVAL; + goto out; + } + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + ret = videobuf_iolock(vq, vb, NULL); + if (ret) + goto fail; + + vb->state = VIDEOBUF_PREPARED; + } + buf->inwork = 0; + + return 0; +fail: + free_buffer(vq, buf, pcdev->vb_mode); +out: + buf->inwork = 0; + return ret; +} + +static void set_dma_dest_params(int dma_ch, struct omap1_cam_buf *buf, + enum omap1_cam_vb_mode vb_mode) +{ + dma_addr_t dma_addr; + unsigned int block_size; + + if (vb_mode == OMAP1_CAM_DMA_CONTIG) { + dma_addr = videobuf_to_dma_contig(&buf->vb); + block_size = buf->vb.size; + } else { + if (WARN_ON(!buf->sgbuf)) { + buf->result = VIDEOBUF_ERROR; + return; + } + dma_addr = sg_dma_address(buf->sgbuf); + if (WARN_ON(!dma_addr)) { + buf->sgbuf = NULL; + buf->result = VIDEOBUF_ERROR; + return; + } + block_size = sg_dma_len(buf->sgbuf); + if (WARN_ON(!block_size)) { + buf->sgbuf = NULL; + buf->result = VIDEOBUF_ERROR; + return; + } + if (unlikely(buf->bytes_left < block_size)) + block_size = buf->bytes_left; + if (WARN_ON(dma_addr & (DMA_FRAME_SIZE(vb_mode) * + DMA_ELEMENT_SIZE - 1))) { + dma_addr = ALIGN(dma_addr, DMA_FRAME_SIZE(vb_mode) * + DMA_ELEMENT_SIZE); + block_size &= ~(DMA_FRAME_SIZE(vb_mode) * + DMA_ELEMENT_SIZE - 1); + } + buf->bytes_left -= block_size; + buf->sgcount++; + } + + omap_set_dma_dest_params(dma_ch, + OMAP_DMA_PORT_EMIFF, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0); + omap_set_dma_transfer_params(dma_ch, + OMAP_DMA_DATA_TYPE_S32, DMA_FRAME_SIZE(vb_mode), + block_size >> (DMA_FRAME_SHIFT(vb_mode) + DMA_ELEMENT_SHIFT), + DMA_SYNC, 0, 0); +} + +static struct omap1_cam_buf *prepare_next_vb(struct omap1_cam_dev *pcdev) +{ + struct omap1_cam_buf *buf; + + /* + * If there is already a buffer pointed out by the pcdev->ready, + * (re)use it, otherwise try to fetch and configure a new one. + */ + buf = pcdev->ready; + if (!buf) { + if (list_empty(&pcdev->capture)) + return buf; + buf = list_entry(pcdev->capture.next, + struct omap1_cam_buf, vb.queue); + buf->vb.state = VIDEOBUF_ACTIVE; + pcdev->ready = buf; + list_del_init(&buf->vb.queue); + } + + if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { + /* + * In CONTIG mode, we can safely enter next buffer parameters + * into the DMA programming register set after the DMA + * has already been activated on the previous buffer + */ + set_dma_dest_params(pcdev->dma_ch, buf, pcdev->vb_mode); + } else { + /* + * In SG mode, the above is not safe since there are probably + * a bunch of sgbufs from previous sglist still pending. + * Instead, mark the sglist fresh for the upcoming + * try_next_sgbuf(). + */ + buf->sgbuf = NULL; + } + + return buf; +} + +static struct scatterlist *try_next_sgbuf(int dma_ch, struct omap1_cam_buf *buf) +{ + struct scatterlist *sgbuf; + + if (likely(buf->sgbuf)) { + /* current sglist is active */ + if (unlikely(!buf->bytes_left)) { + /* indicate sglist complete */ + sgbuf = NULL; + } else { + /* process next sgbuf */ + sgbuf = sg_next(buf->sgbuf); + if (WARN_ON(!sgbuf)) { + buf->result = VIDEOBUF_ERROR; + } else if (WARN_ON(!sg_dma_len(sgbuf))) { + sgbuf = NULL; + buf->result = VIDEOBUF_ERROR; + } + } + buf->sgbuf = sgbuf; + } else { + /* sglist is fresh, initialize it before using */ + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + + sgbuf = dma->sglist; + if (!(WARN_ON(!sgbuf))) { + buf->sgbuf = sgbuf; + buf->sgcount = 0; + buf->bytes_left = buf->vb.size; + buf->result = VIDEOBUF_DONE; + } + } + if (sgbuf) + /* + * Put our next sgbuf parameters (address, size) + * into the DMA programming register set. + */ + set_dma_dest_params(dma_ch, buf, OMAP1_CAM_DMA_SG); + + return sgbuf; +} + +static void start_capture(struct omap1_cam_dev *pcdev) +{ + struct omap1_cam_buf *buf = pcdev->active; + u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + u32 mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN; + + if (WARN_ON(!buf)) + return; + + /* + * Enable start of frame interrupt, which we will use for activating + * our end of frame watchdog when capture actually starts. + */ + mode |= EN_V_UP; + + if (unlikely(ctrlclock & LCLK_EN)) + /* stop pixel clock before FIFO reset */ + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + /* reset FIFO */ + CAM_WRITE(pcdev, MODE, mode | RAZ_FIFO); + + omap_start_dma(pcdev->dma_ch); + + if (pcdev->vb_mode == OMAP1_CAM_DMA_SG) { + /* + * In SG mode, it's a good moment for fetching next sgbuf + * from the current sglist and, if available, already putting + * its parameters into the DMA programming register set. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + } + + /* (re)enable pixel clock */ + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | LCLK_EN); + /* release FIFO reset */ + CAM_WRITE(pcdev, MODE, mode); +} + +static void suspend_capture(struct omap1_cam_dev *pcdev) +{ + u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + omap_stop_dma(pcdev->dma_ch); +} + +static void disable_capture(struct omap1_cam_dev *pcdev) +{ + u32 mode = CAM_READ_CACHE(pcdev, MODE); + + CAM_WRITE(pcdev, MODE, mode & ~(IRQ_MASK | DMA)); +} + +static void omap1_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct omap1_cam_dev *pcdev = ici->priv; + struct omap1_cam_buf *buf; + u32 mode; + + list_add_tail(&vb->queue, &pcdev->capture); + vb->state = VIDEOBUF_QUEUED; + + if (pcdev->active) { + /* + * Capture in progress, so don't touch pcdev->ready even if + * empty. Since the transfer of the DMA programming register set + * content to the DMA working register set is done automatically + * by the DMA hardware, this can pretty well happen while we + * are keeping the lock here. Leave fetching it from the queue + * to be done when a next DMA interrupt occures instead. + */ + return; + } + + WARN_ON(pcdev->ready); + + buf = prepare_next_vb(pcdev); + if (WARN_ON(!buf)) + return; + + pcdev->active = buf; + pcdev->ready = NULL; + + dev_dbg(icd->parent, + "%s: capture not active, setup FIFO, start DMA\n", __func__); + mode = CAM_READ_CACHE(pcdev, MODE) & ~THRESHOLD_MASK; + mode |= THRESHOLD_LEVEL(pcdev->vb_mode) << THRESHOLD_SHIFT; + CAM_WRITE(pcdev, MODE, mode | EN_FIFO_FULL | DMA); + + if (pcdev->vb_mode == OMAP1_CAM_DMA_SG) { + /* + * In SG mode, the above prepare_next_vb() didn't actually + * put anything into the DMA programming register set, + * so we have to do it now, before activating DMA. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + } + + start_capture(pcdev); +} + +static void omap1_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct omap1_cam_buf *buf = + container_of(vb, struct omap1_cam_buf, vb); + struct soc_camera_device *icd = vq->priv_data; + struct device *dev = icd->parent; + struct soc_camera_host *ici = to_soc_camera_host(dev); + struct omap1_cam_dev *pcdev = ici->priv; + + switch (vb->state) { + case VIDEOBUF_DONE: + dev_dbg(dev, "%s (done)\n", __func__); + break; + case VIDEOBUF_ACTIVE: + dev_dbg(dev, "%s (active)\n", __func__); + break; + case VIDEOBUF_QUEUED: + dev_dbg(dev, "%s (queued)\n", __func__); + break; + case VIDEOBUF_PREPARED: + dev_dbg(dev, "%s (prepared)\n", __func__); + break; + default: + dev_dbg(dev, "%s (unknown %d)\n", __func__, vb->state); + break; + } + + free_buffer(vq, buf, pcdev->vb_mode); +} + +static void videobuf_done(struct omap1_cam_dev *pcdev, + enum videobuf_state result) +{ + struct omap1_cam_buf *buf = pcdev->active; + struct videobuf_buffer *vb; + struct device *dev = pcdev->soc_host.icd->parent; + + if (WARN_ON(!buf)) { + suspend_capture(pcdev); + disable_capture(pcdev); + return; + } + + if (result == VIDEOBUF_ERROR) + suspend_capture(pcdev); + + vb = &buf->vb; + if (waitqueue_active(&vb->done)) { + if (!pcdev->ready && result != VIDEOBUF_ERROR) { + /* + * No next buffer has been entered into the DMA + * programming register set on time (could be done only + * while the previous DMA interurpt was processed, not + * later), so the last DMA block, be it a whole buffer + * if in CONTIG or its last sgbuf if in SG mode, is + * about to be reused by the just autoreinitialized DMA + * engine, and overwritten with next frame data. Best we + * can do is stopping the capture as soon as possible, + * hopefully before the next frame start. + */ + suspend_capture(pcdev); + } + vb->state = result; + v4l2_get_timestamp(&vb->ts); + if (result != VIDEOBUF_ERROR) + vb->field_count++; + wake_up(&vb->done); + + /* shift in next buffer */ + buf = pcdev->ready; + pcdev->active = buf; + pcdev->ready = NULL; + + if (!buf) { + /* + * No next buffer was ready on time (see above), so + * indicate error condition to force capture restart or + * stop, depending on next buffer already queued or not. + */ + result = VIDEOBUF_ERROR; + prepare_next_vb(pcdev); + + buf = pcdev->ready; + pcdev->active = buf; + pcdev->ready = NULL; + } + } else if (pcdev->ready) { + /* + * In both CONTIG and SG mode, the DMA engine has possibly + * been already autoreinitialized with the preprogrammed + * pcdev->ready buffer. We can either accept this fact + * and just swap the buffers, or provoke an error condition + * and restart capture. The former seems less intrusive. + */ + dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n", + __func__); + pcdev->active = pcdev->ready; + + if (pcdev->vb_mode == OMAP1_CAM_DMA_SG) { + /* + * In SG mode, we have to make sure that the buffer we + * are putting back into the pcdev->ready is marked + * fresh. + */ + buf->sgbuf = NULL; + } + pcdev->ready = buf; + + buf = pcdev->active; + } else { + /* + * No next buffer has been entered into + * the DMA programming register set on time. + */ + if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { + /* + * In CONTIG mode, the DMA engine has already been + * reinitialized with the current buffer. Best we can do + * is not touching it. + */ + dev_dbg(dev, + "%s: nobody waiting on videobuf, reuse it\n", + __func__); + } else { + /* + * In SG mode, the DMA engine has just been + * autoreinitialized with the last sgbuf from the + * current list. Restart capture in order to transfer + * next frame start into the first sgbuf, not the last + * one. + */ + if (result != VIDEOBUF_ERROR) { + suspend_capture(pcdev); + result = VIDEOBUF_ERROR; + } + } + } + + if (!buf) { + dev_dbg(dev, "%s: no more videobufs, stop capture\n", __func__); + disable_capture(pcdev); + return; + } + + if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { + /* + * In CONTIG mode, the current buffer parameters had already + * been entered into the DMA programming register set while the + * buffer was fetched with prepare_next_vb(), they may have also + * been transferred into the runtime set and already active if + * the DMA still running. + */ + } else { + /* In SG mode, extra steps are required */ + if (result == VIDEOBUF_ERROR) + /* make sure we (re)use sglist from start on error */ + buf->sgbuf = NULL; + + /* + * In any case, enter the next sgbuf parameters into the DMA + * programming register set. They will be used either during + * nearest DMA autoreinitialization or, in case of an error, + * on DMA startup below. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + } + + if (result == VIDEOBUF_ERROR) { + dev_dbg(dev, "%s: videobuf error; reset FIFO, restart DMA\n", + __func__); + start_capture(pcdev); + /* + * In SG mode, the above also resulted in the next sgbuf + * parameters being entered into the DMA programming register + * set, making them ready for next DMA autoreinitialization. + */ + } + + /* + * Finally, try fetching next buffer. + * In CONTIG mode, it will also enter it into the DMA programming + * register set, making it ready for next DMA autoreinitialization. + */ + prepare_next_vb(pcdev); +} + +static void dma_isr(int channel, unsigned short status, void *data) +{ + struct omap1_cam_dev *pcdev = data; + struct omap1_cam_buf *buf = pcdev->active; + unsigned long flags; + + spin_lock_irqsave(&pcdev->lock, flags); + + if (WARN_ON(!buf)) { + suspend_capture(pcdev); + disable_capture(pcdev); + goto out; + } + + if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { + /* + * In CONTIG mode, assume we have just managed to collect the + * whole frame, hopefully before our end of frame watchdog is + * triggered. Then, all we have to do is disabling the watchdog + * for this frame, and calling videobuf_done() with success + * indicated. + */ + CAM_WRITE(pcdev, MODE, + CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN); + videobuf_done(pcdev, VIDEOBUF_DONE); + } else { + /* + * In SG mode, we have to process every sgbuf from the current + * sglist, one after another. + */ + if (buf->sgbuf) { + /* + * Current sglist not completed yet, try fetching next + * sgbuf, hopefully putting it into the DMA programming + * register set, making it ready for next DMA + * autoreinitialization. + */ + try_next_sgbuf(pcdev->dma_ch, buf); + if (buf->sgbuf) + goto out; + + /* + * No more sgbufs left in the current sglist. This + * doesn't mean that the whole videobuffer is already + * complete, but only that the last sgbuf from the + * current sglist is about to be filled. It will be + * ready on next DMA interrupt, signalled with the + * buf->sgbuf set back to NULL. + */ + if (buf->result != VIDEOBUF_ERROR) { + /* + * Video frame collected without errors so far, + * we can prepare for collecting a next one + * as soon as DMA gets autoreinitialized + * after the current (last) sgbuf is completed. + */ + buf = prepare_next_vb(pcdev); + if (!buf) + goto out; + + try_next_sgbuf(pcdev->dma_ch, buf); + goto out; + } + } + /* end of videobuf */ + videobuf_done(pcdev, buf->result); + } + +out: + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static irqreturn_t cam_isr(int irq, void *data) +{ + struct omap1_cam_dev *pcdev = data; + struct device *dev = pcdev->soc_host.icd->parent; + struct omap1_cam_buf *buf = pcdev->active; + u32 it_status; + unsigned long flags; + + it_status = CAM_READ(pcdev, IT_STATUS); + if (!it_status) + return IRQ_NONE; + + spin_lock_irqsave(&pcdev->lock, flags); + + if (WARN_ON(!buf)) { + dev_warn(dev, "%s: unhandled camera interrupt, status == %#x\n", + __func__, it_status); + suspend_capture(pcdev); + disable_capture(pcdev); + goto out; + } + + if (unlikely(it_status & FIFO_FULL)) { + dev_warn(dev, "%s: FIFO overflow\n", __func__); + + } else if (it_status & V_DOWN) { + /* end of video frame watchdog */ + if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { + /* + * In CONTIG mode, the watchdog is disabled with + * successful DMA end of block interrupt, and reenabled + * on next frame start. If we get here, there is nothing + * to check, we must be out of sync. + */ + } else { + if (buf->sgcount == 2) { + /* + * If exactly 2 sgbufs from the next sglist have + * been programmed into the DMA engine (the + * first one already transferred into the DMA + * runtime register set, the second one still + * in the programming set), then we are in sync. + */ + goto out; + } + } + dev_notice(dev, "%s: unexpected end of video frame\n", + __func__); + + } else if (it_status & V_UP) { + u32 mode; + + if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { + /* + * In CONTIG mode, we need this interrupt every frame + * in oredr to reenable our end of frame watchdog. + */ + mode = CAM_READ_CACHE(pcdev, MODE); + } else { + /* + * In SG mode, the below enabled end of frame watchdog + * is kept on permanently, so we can turn this one shot + * setup off. + */ + mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_UP; + } + + if (!(mode & EN_V_DOWN)) { + /* (re)enable end of frame watchdog interrupt */ + mode |= EN_V_DOWN; + } + CAM_WRITE(pcdev, MODE, mode); + goto out; + + } else { + dev_warn(dev, "%s: unhandled camera interrupt, status == %#x\n", + __func__, it_status); + goto out; + } + + videobuf_done(pcdev, VIDEOBUF_ERROR); +out: + spin_unlock_irqrestore(&pcdev->lock, flags); + return IRQ_HANDLED; +} + +static struct videobuf_queue_ops omap1_videobuf_ops = { + .buf_setup = omap1_videobuf_setup, + .buf_prepare = omap1_videobuf_prepare, + .buf_queue = omap1_videobuf_queue, + .buf_release = omap1_videobuf_release, +}; + + +/* + * SOC Camera host operations + */ + +static void sensor_reset(struct omap1_cam_dev *pcdev, bool reset) +{ + /* apply/release camera sensor reset if requested by platform data */ + if (pcdev->pflags & OMAP1_CAMERA_RST_HIGH) + CAM_WRITE(pcdev, GPIO, reset); + else if (pcdev->pflags & OMAP1_CAMERA_RST_LOW) + CAM_WRITE(pcdev, GPIO, !reset); +} + +static int omap1_cam_add_device(struct soc_camera_device *icd) +{ + dev_dbg(icd->parent, "OMAP1 Camera driver attached to camera %d\n", + icd->devnum); + + return 0; +} + +static void omap1_cam_remove_device(struct soc_camera_device *icd) +{ + dev_dbg(icd->parent, + "OMAP1 Camera driver detached from camera %d\n", icd->devnum); +} + +/* + * The following two functions absolutely depend on the fact, that + * there can be only one camera on OMAP1 camera sensor interface + */ +static int omap1_cam_clock_start(struct soc_camera_host *ici) +{ + struct omap1_cam_dev *pcdev = ici->priv; + u32 ctrlclock; + + clk_enable(pcdev->clk); + + /* setup sensor clock */ + ctrlclock = CAM_READ(pcdev, CTRLCLOCK); + ctrlclock &= ~(CAMEXCLK_EN | MCLK_EN | DPLL_EN); + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + ctrlclock &= ~FOSCMOD_MASK; + switch (pcdev->camexclk) { + case 6000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_6MHz; + break; + case 8000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_8MHz | DPLL_EN; + break; + case 9600000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_9_6MHz | DPLL_EN; + break; + case 12000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_12MHz; + break; + case 24000000: + ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN; + default: + break; + } + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~DPLL_EN); + + /* enable internal clock */ + ctrlclock |= MCLK_EN; + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + sensor_reset(pcdev, false); + + return 0; +} + +static void omap1_cam_clock_stop(struct soc_camera_host *ici) +{ + struct omap1_cam_dev *pcdev = ici->priv; + u32 ctrlclock; + + suspend_capture(pcdev); + disable_capture(pcdev); + + sensor_reset(pcdev, true); + + /* disable and release system clocks */ + ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + ctrlclock &= ~(MCLK_EN | DPLL_EN | CAMEXCLK_EN); + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + ctrlclock = (ctrlclock & ~FOSCMOD_MASK) | FOSCMOD_12MHz; + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | MCLK_EN); + + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~MCLK_EN); + + clk_disable(pcdev->clk); +} + +/* Duplicate standard formats based on host capability of byte swapping */ +static const struct soc_mbus_lookup omap1_cam_formats[] = { +{ + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_YUYV, + .name = "YUYV", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_YVYU, + .name = "YVYU", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_UYVY, + .name = "UYVY", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_VYUY, + .name = "VYUY", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB555, + .name = "RGB555", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB555X, + .name = "RGB555X", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB565, + .name = "RGB565", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB565X, + .name = "RGB565X", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_2X8_PADHI, + .order = SOC_MBUS_ORDER_BE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, +}; + +static int omap1_cam_get_formats(struct soc_camera_device *icd, + unsigned int idx, struct soc_camera_format_xlate *xlate) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + int formats = 0, ret; + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .index = idx, + }; + const struct soc_mbus_pixelfmt *fmt; + + ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code); + if (ret < 0) + /* No more formats */ + return 0; + + fmt = soc_mbus_get_fmtdesc(code.code); + if (!fmt) { + dev_warn(dev, "%s: unsupported format code #%d: %d\n", __func__, + idx, code.code); + return 0; + } + + /* Check support for the requested bits-per-sample */ + if (fmt->bits_per_sample != 8) + return 0; + + switch (code.code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + case MEDIA_BUS_FMT_RGB565_2X8_BE: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + formats++; + if (xlate) { + xlate->host_fmt = soc_mbus_find_fmtdesc(code.code, + omap1_cam_formats, + ARRAY_SIZE(omap1_cam_formats)); + xlate->code = code.code; + xlate++; + dev_dbg(dev, + "%s: providing format %s as byte swapped code #%d\n", + __func__, xlate->host_fmt->name, code.code); + } + default: + if (xlate) + dev_dbg(dev, + "%s: providing format %s in pass-through mode\n", + __func__, fmt->name); + } + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code.code; + xlate++; + } + + return formats; +} + +static bool is_dma_aligned(s32 bytes_per_line, unsigned int height, + enum omap1_cam_vb_mode vb_mode) +{ + int size = bytes_per_line * height; + + return IS_ALIGNED(bytes_per_line, DMA_ELEMENT_SIZE) && + IS_ALIGNED(size, DMA_FRAME_SIZE(vb_mode) * DMA_ELEMENT_SIZE); +} + +static int dma_align(int *width, int *height, + const struct soc_mbus_pixelfmt *fmt, + enum omap1_cam_vb_mode vb_mode, bool enlarge) +{ + s32 bytes_per_line = soc_mbus_bytes_per_line(*width, fmt); + + if (bytes_per_line < 0) + return bytes_per_line; + + if (!is_dma_aligned(bytes_per_line, *height, vb_mode)) { + unsigned int pxalign = __fls(bytes_per_line / *width); + unsigned int salign = DMA_FRAME_SHIFT(vb_mode) + + DMA_ELEMENT_SHIFT - pxalign; + unsigned int incr = enlarge << salign; + + v4l_bound_align_image(width, 1, *width + incr, 0, + height, 1, *height + incr, 0, salign); + return 0; + } + return 1; +} + +#define subdev_call_with_sense(pcdev, dev, icd, sd, op, function, args...) \ +({ \ + struct soc_camera_sense sense = { \ + .master_clock = pcdev->camexclk, \ + .pixel_clock_max = 0, \ + }; \ + int __ret; \ + \ + if (pcdev->pdata) \ + sense.pixel_clock_max = pcdev->pdata->lclk_khz_max * 1000; \ + icd->sense = &sense; \ + __ret = v4l2_subdev_call(sd, op, function, ##args); \ + icd->sense = NULL; \ + \ + if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) { \ + if (sense.pixel_clock > sense.pixel_clock_max) { \ + dev_err(dev, \ + "%s: pixel clock %lu set by the camera too high!\n", \ + __func__, sense.pixel_clock); \ + __ret = -EINVAL; \ + } \ + } \ + __ret; \ +}) + +static int set_format(struct omap1_cam_dev *pcdev, struct device *dev, + struct soc_camera_device *icd, struct v4l2_subdev *sd, + struct v4l2_subdev_format *format, + const struct soc_camera_format_xlate *xlate) +{ + s32 bytes_per_line; + struct v4l2_mbus_framefmt *mf = &format->format; + int ret = subdev_call_with_sense(pcdev, dev, icd, sd, pad, set_fmt, NULL, format); + + if (ret < 0) { + dev_err(dev, "%s: set_fmt failed\n", __func__); + return ret; + } + + if (mf->code != xlate->code) { + dev_err(dev, "%s: unexpected pixel code change\n", __func__); + return -EINVAL; + } + + bytes_per_line = soc_mbus_bytes_per_line(mf->width, xlate->host_fmt); + if (bytes_per_line < 0) { + dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n", + __func__); + return bytes_per_line; + } + + if (!is_dma_aligned(bytes_per_line, mf->height, pcdev->vb_mode)) { + dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n", + __func__, mf->width, mf->height); + return -EINVAL; + } + return 0; +} + +static int omap1_cam_set_crop(struct soc_camera_device *icd, + const struct v4l2_crop *crop) +{ + const struct v4l2_rect *rect = &crop->c; + const struct soc_camera_format_xlate *xlate = icd->current_fmt; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + struct soc_camera_host *ici = to_soc_camera_host(dev); + struct omap1_cam_dev *pcdev = ici->priv; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &fmt.format; + int ret; + + ret = subdev_call_with_sense(pcdev, dev, icd, sd, video, s_crop, crop); + if (ret < 0) { + dev_warn(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__, + rect->width, rect->height, rect->left, rect->top); + return ret; + } + + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret < 0) { + dev_warn(dev, "%s: failed to fetch current format\n", __func__); + return ret; + } + + ret = dma_align(&mf->width, &mf->height, xlate->host_fmt, pcdev->vb_mode, + false); + if (ret < 0) { + dev_err(dev, "%s: failed to align %ux%u %s with DMA\n", + __func__, mf->width, mf->height, + xlate->host_fmt->name); + return ret; + } + + if (!ret) { + /* sensor returned geometry not DMA aligned, trying to fix */ + ret = set_format(pcdev, dev, icd, sd, &fmt, xlate); + if (ret < 0) { + dev_err(dev, "%s: failed to set format\n", __func__); + return ret; + } + } + + icd->user_width = mf->width; + icd->user_height = mf->height; + + return 0; +} + +static int omap1_cam_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct device *dev = icd->parent; + struct soc_camera_host *ici = to_soc_camera_host(dev); + struct omap1_cam_dev *pcdev = ici->priv; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); + if (!xlate) { + dev_warn(dev, "%s: format %#x not found\n", __func__, + pix->pixelformat); + return -EINVAL; + } + + mf->width = pix->width; + mf->height = pix->height; + mf->field = pix->field; + mf->colorspace = pix->colorspace; + mf->code = xlate->code; + + ret = dma_align(&mf->width, &mf->height, xlate->host_fmt, pcdev->vb_mode, + true); + if (ret < 0) { + dev_err(dev, "%s: failed to align %ux%u %s with DMA\n", + __func__, pix->width, pix->height, + xlate->host_fmt->name); + return ret; + } + + ret = set_format(pcdev, dev, icd, sd, &format, xlate); + if (ret < 0) { + dev_err(dev, "%s: failed to set format\n", __func__); + return ret; + } + + pix->width = mf->width; + pix->height = mf->height; + pix->field = mf->field; + pix->colorspace = mf->colorspace; + icd->current_fmt = xlate; + + return 0; +} + +static int omap1_cam_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + const struct soc_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + int ret; + /* TODO: limit to mx1 hardware capabilities */ + + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); + if (!xlate) { + dev_warn(icd->parent, "Format %#x not found\n", + pix->pixelformat); + return -EINVAL; + } + + mf->width = pix->width; + mf->height = pix->height; + mf->field = pix->field; + mf->colorspace = pix->colorspace; + mf->code = xlate->code; + + /* limit to sensor capabilities */ + ret = v4l2_subdev_call(sd, pad, set_fmt, &pad_cfg, &format); + if (ret < 0) + return ret; + + pix->width = mf->width; + pix->height = mf->height; + pix->field = mf->field; + pix->colorspace = mf->colorspace; + + return 0; +} + +static bool sg_mode; + +/* + * Local mmap_mapper wrapper, + * used for detecting videobuf-dma-contig buffer allocation failures + * and switching to videobuf-dma-sg automatically for future attempts. + */ +static int omap1_cam_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct soc_camera_device *icd = q->priv_data; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct omap1_cam_dev *pcdev = ici->priv; + int ret; + + ret = pcdev->mmap_mapper(q, buf, vma); + + if (ret == -ENOMEM) + sg_mode = true; + + return ret; +} + +static void omap1_cam_init_videobuf(struct videobuf_queue *q, + struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct omap1_cam_dev *pcdev = ici->priv; + + if (!sg_mode) + videobuf_queue_dma_contig_init(q, &omap1_videobuf_ops, + icd->parent, &pcdev->lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, + sizeof(struct omap1_cam_buf), icd, &ici->host_lock); + else + videobuf_queue_sg_init(q, &omap1_videobuf_ops, + icd->parent, &pcdev->lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, + sizeof(struct omap1_cam_buf), icd, &ici->host_lock); + + /* use videobuf mode (auto)selected with the module parameter */ + pcdev->vb_mode = sg_mode ? OMAP1_CAM_DMA_SG : OMAP1_CAM_DMA_CONTIG; + + /* + * Ensure we substitute the videobuf-dma-contig version of the + * mmap_mapper() callback with our own wrapper, used for switching + * automatically to videobuf-dma-sg on buffer allocation failure. + */ + if (!sg_mode && q->int_ops->mmap_mapper != omap1_cam_mmap_mapper) { + pcdev->mmap_mapper = q->int_ops->mmap_mapper; + q->int_ops->mmap_mapper = omap1_cam_mmap_mapper; + } +} + +static int omap1_cam_reqbufs(struct soc_camera_device *icd, + struct v4l2_requestbuffers *p) +{ + int i; + + /* + * This is for locking debugging only. I removed spinlocks and now I + * check whether .prepare is ever called on a linked buffer, or whether + * a dma IRQ can occur for an in-work or unlinked buffer. Until now + * it hadn't triggered + */ + for (i = 0; i < p->count; i++) { + struct omap1_cam_buf *buf = container_of(icd->vb_vidq.bufs[i], + struct omap1_cam_buf, vb); + buf->inwork = 0; + INIT_LIST_HEAD(&buf->vb.queue); + } + + return 0; +} + +static int omap1_cam_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + /* cap->name is set by the friendly caller:-> */ + strlcpy(cap->card, "OMAP1 Camera", sizeof(cap->card)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int omap1_cam_set_bus_param(struct soc_camera_device *icd) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct device *dev = icd->parent; + struct soc_camera_host *ici = to_soc_camera_host(dev); + struct omap1_cam_dev *pcdev = ici->priv; + u32 pixfmt = icd->current_fmt->host_fmt->fourcc; + const struct soc_camera_format_xlate *xlate; + const struct soc_mbus_pixelfmt *fmt; + struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; + unsigned long common_flags; + u32 ctrlclock, mode; + int ret; + + ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); + if (!ret) { + common_flags = soc_mbus_config_compatible(&cfg, SOCAM_BUS_FLAGS); + if (!common_flags) { + dev_warn(dev, + "Flags incompatible: camera 0x%x, host 0x%x\n", + cfg.flags, SOCAM_BUS_FLAGS); + return -EINVAL; + } + } else if (ret != -ENOIOCTLCMD) { + return ret; + } else { + common_flags = SOCAM_BUS_FLAGS; + } + + /* Make choices, possibly based on platform configuration */ + if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) && + (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) { + if (!pcdev->pdata || + pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING) + common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING; + else + common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING; + } + + cfg.flags = common_flags; + ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(dev, "camera s_mbus_config(0x%lx) returned %d\n", + common_flags, ret); + return ret; + } + + ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); + if (ctrlclock & LCLK_EN) + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + + if (common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) { + dev_dbg(dev, "CTRLCLOCK_REG |= POLCLK\n"); + ctrlclock |= POLCLK; + } else { + dev_dbg(dev, "CTRLCLOCK_REG &= ~POLCLK\n"); + ctrlclock &= ~POLCLK; + } + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); + + if (ctrlclock & LCLK_EN) + CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); + + /* select bus endianness */ + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + fmt = xlate->host_fmt; + + mode = CAM_READ(pcdev, MODE) & ~(RAZ_FIFO | IRQ_MASK | DMA); + if (fmt->order == SOC_MBUS_ORDER_LE) { + dev_dbg(dev, "MODE_REG &= ~ORDERCAMD\n"); + CAM_WRITE(pcdev, MODE, mode & ~ORDERCAMD); + } else { + dev_dbg(dev, "MODE_REG |= ORDERCAMD\n"); + CAM_WRITE(pcdev, MODE, mode | ORDERCAMD); + } + + return 0; +} + +static unsigned int omap1_cam_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_device *icd = file->private_data; + struct omap1_cam_buf *buf; + + buf = list_entry(icd->vb_vidq.stream.next, struct omap1_cam_buf, + vb.stream); + + poll_wait(file, &buf->vb.done, pt); + + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + + return 0; +} + +static struct soc_camera_host_ops omap1_host_ops = { + .owner = THIS_MODULE, + .add = omap1_cam_add_device, + .remove = omap1_cam_remove_device, + .clock_start = omap1_cam_clock_start, + .clock_stop = omap1_cam_clock_stop, + .get_formats = omap1_cam_get_formats, + .set_crop = omap1_cam_set_crop, + .set_fmt = omap1_cam_set_fmt, + .try_fmt = omap1_cam_try_fmt, + .init_videobuf = omap1_cam_init_videobuf, + .reqbufs = omap1_cam_reqbufs, + .querycap = omap1_cam_querycap, + .set_bus_param = omap1_cam_set_bus_param, + .poll = omap1_cam_poll, +}; + +static int omap1_cam_probe(struct platform_device *pdev) +{ + struct omap1_cam_dev *pcdev; + struct resource *res; + struct clk *clk; + void __iomem *base; + unsigned int irq; + int err = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || (int)irq <= 0) { + err = -ENODEV; + goto exit; + } + + clk = clk_get(&pdev->dev, "armper_ck"); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto exit; + } + + pcdev = kzalloc(sizeof(*pcdev) + resource_size(res), GFP_KERNEL); + if (!pcdev) { + dev_err(&pdev->dev, "Could not allocate pcdev\n"); + err = -ENOMEM; + goto exit_put_clk; + } + + pcdev->res = res; + pcdev->clk = clk; + + pcdev->pdata = pdev->dev.platform_data; + if (pcdev->pdata) { + pcdev->pflags = pcdev->pdata->flags; + pcdev->camexclk = pcdev->pdata->camexclk_khz * 1000; + } + + switch (pcdev->camexclk) { + case 6000000: + case 8000000: + case 9600000: + case 12000000: + case 24000000: + break; + default: + /* pcdev->camexclk != 0 => pcdev->pdata != NULL */ + dev_warn(&pdev->dev, + "Incorrect sensor clock frequency %ld kHz, " + "should be one of 0, 6, 8, 9.6, 12 or 24 MHz, " + "please correct your platform data\n", + pcdev->pdata->camexclk_khz); + pcdev->camexclk = 0; + case 0: + dev_info(&pdev->dev, "Not providing sensor clock\n"); + } + + INIT_LIST_HEAD(&pcdev->capture); + spin_lock_init(&pcdev->lock); + + /* + * Request the region. + */ + if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) { + err = -EBUSY; + goto exit_kfree; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + err = -ENOMEM; + goto exit_release; + } + pcdev->irq = irq; + pcdev->base = base; + + sensor_reset(pcdev, true); + + err = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, DRIVER_NAME, + dma_isr, (void *)pcdev, &pcdev->dma_ch); + if (err < 0) { + dev_err(&pdev->dev, "Can't request DMA for OMAP1 Camera\n"); + err = -EBUSY; + goto exit_iounmap; + } + dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_ch); + + /* preconfigure DMA */ + omap_set_dma_src_params(pcdev->dma_ch, OMAP_DMA_PORT_TIPB, + OMAP_DMA_AMODE_CONSTANT, res->start + REG_CAMDATA, + 0, 0); + omap_set_dma_dest_burst_mode(pcdev->dma_ch, OMAP_DMA_DATA_BURST_4); + /* setup DMA autoinitialization */ + omap_dma_link_lch(pcdev->dma_ch, pcdev->dma_ch); + + err = request_irq(pcdev->irq, cam_isr, 0, DRIVER_NAME, pcdev); + if (err) { + dev_err(&pdev->dev, "Camera interrupt register failed\n"); + goto exit_free_dma; + } + + pcdev->soc_host.drv_name = DRIVER_NAME; + pcdev->soc_host.ops = &omap1_host_ops; + pcdev->soc_host.priv = pcdev; + pcdev->soc_host.v4l2_dev.dev = &pdev->dev; + pcdev->soc_host.nr = pdev->id; + + err = soc_camera_host_register(&pcdev->soc_host); + if (err) + goto exit_free_irq; + + dev_info(&pdev->dev, "OMAP1 Camera Interface driver loaded\n"); + + return 0; + +exit_free_irq: + free_irq(pcdev->irq, pcdev); +exit_free_dma: + omap_free_dma(pcdev->dma_ch); +exit_iounmap: + iounmap(base); +exit_release: + release_mem_region(res->start, resource_size(res)); +exit_kfree: + kfree(pcdev); +exit_put_clk: + clk_put(clk); +exit: + return err; +} + +static int omap1_cam_remove(struct platform_device *pdev) +{ + struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); + struct omap1_cam_dev *pcdev = container_of(soc_host, + struct omap1_cam_dev, soc_host); + struct resource *res; + + free_irq(pcdev->irq, pcdev); + + omap_free_dma(pcdev->dma_ch); + + soc_camera_host_unregister(soc_host); + + iounmap(pcdev->base); + + res = pcdev->res; + release_mem_region(res->start, resource_size(res)); + + clk_put(pcdev->clk); + + kfree(pcdev); + + dev_info(&pdev->dev, "OMAP1 Camera Interface driver unloaded\n"); + + return 0; +} + +static struct platform_driver omap1_cam_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = omap1_cam_probe, + .remove = omap1_cam_remove, +}; + +module_platform_driver(omap1_cam_driver); + +module_param(sg_mode, bool, 0644); +MODULE_PARM_DESC(sg_mode, "videobuf mode, 0: dma-contig (default), 1: dma-sg"); + +MODULE_DESCRIPTION("OMAP1 Camera Interface driver"); +MODULE_AUTHOR("Janusz Krzysztofik "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/staging/media/omap4iss/iss.c b/drivers/staging/media/omap4iss/iss.c index 30b473cfb..c5a5138b3 100644 --- a/drivers/staging/media/omap4iss/iss.c +++ b/drivers/staging/media/omap4iss/iss.c @@ -362,215 +362,6 @@ static irqreturn_t iss_isr(int irq, void *_iss) return IRQ_HANDLED; } -/* ----------------------------------------------------------------------------- - * Pipeline power management - * - * Entities must be powered up when part of a pipeline that contains at least - * one open video device node. - * - * To achieve this use the entity use_count field to track the number of users. - * For entities corresponding to video device nodes the use_count field stores - * the users count of the node. For entities corresponding to subdevs the - * use_count field stores the total number of users of all video device nodes - * in the pipeline. - * - * The omap4iss_pipeline_pm_use() function must be called in the open() and - * close() handlers of video device nodes. It increments or decrements the use - * count of all subdev entities in the pipeline. - * - * To react to link management on powered pipelines, the link setup notification - * callback updates the use count of all entities in the source and sink sides - * of the link. - */ - -/* - * iss_pipeline_pm_use_count - Count the number of users of a pipeline - * @entity: The entity - * - * Return the total number of users of all video device nodes in the pipeline. - */ -static int iss_pipeline_pm_use_count(struct media_entity *entity, - struct media_entity_graph *graph) -{ - int use = 0; - - media_entity_graph_walk_start(graph, entity); - - while ((entity = media_entity_graph_walk_next(graph))) { - if (is_media_entity_v4l2_io(entity)) - use += entity->use_count; - } - - return use; -} - -/* - * iss_pipeline_pm_power_one - Apply power change to an entity - * @entity: The entity - * @change: Use count change - * - * Change the entity use count by @change. If the entity is a subdev update its - * power state by calling the core::s_power operation when the use count goes - * from 0 to != 0 or from != 0 to 0. - * - * Return 0 on success or a negative error code on failure. - */ -static int iss_pipeline_pm_power_one(struct media_entity *entity, int change) -{ - struct v4l2_subdev *subdev; - - subdev = is_media_entity_v4l2_subdev(entity) - ? media_entity_to_v4l2_subdev(entity) : NULL; - - if (entity->use_count == 0 && change > 0 && subdev) { - int ret; - - ret = v4l2_subdev_call(subdev, core, s_power, 1); - if (ret < 0 && ret != -ENOIOCTLCMD) - return ret; - } - - entity->use_count += change; - WARN_ON(entity->use_count < 0); - - if (entity->use_count == 0 && change < 0 && subdev) - v4l2_subdev_call(subdev, core, s_power, 0); - - return 0; -} - -/* - * iss_pipeline_pm_power - Apply power change to all entities in a pipeline - * @entity: The entity - * @change: Use count change - * - * Walk the pipeline to update the use count and the power state of all non-node - * entities. - * - * Return 0 on success or a negative error code on failure. - */ -static int iss_pipeline_pm_power(struct media_entity *entity, int change, - struct media_entity_graph *graph) -{ - struct media_entity *first = entity; - int ret = 0; - - if (!change) - return 0; - - media_entity_graph_walk_start(graph, entity); - - while (!ret && (entity = media_entity_graph_walk_next(graph))) - if (is_media_entity_v4l2_subdev(entity)) - ret = iss_pipeline_pm_power_one(entity, change); - - if (!ret) - return 0; - - media_entity_graph_walk_start(graph, first); - - while ((first = media_entity_graph_walk_next(graph)) && - first != entity) - if (is_media_entity_v4l2_subdev(first)) - iss_pipeline_pm_power_one(first, -change); - - return ret; -} - -/* - * omap4iss_pipeline_pm_use - Update the use count of an entity - * @entity: The entity - * @use: Use (1) or stop using (0) the entity - * - * Update the use count of all entities in the pipeline and power entities on or - * off accordingly. - * - * Return 0 on success or a negative error code on failure. Powering entities - * off is assumed to never fail. No failure can occur when the use parameter is - * set to 0. - */ -int omap4iss_pipeline_pm_use(struct media_entity *entity, int use, - struct media_entity_graph *graph) -{ - int change = use ? 1 : -1; - int ret; - - mutex_lock(&entity->graph_obj.mdev->graph_mutex); - - /* Apply use count to node. */ - entity->use_count += change; - WARN_ON(entity->use_count < 0); - - /* Apply power change to connected non-nodes. */ - ret = iss_pipeline_pm_power(entity, change, graph); - if (ret < 0) - entity->use_count -= change; - - mutex_unlock(&entity->graph_obj.mdev->graph_mutex); - - return ret; -} - -/* - * iss_pipeline_link_notify - Link management notification callback - * @link: The link - * @flags: New link flags that will be applied - * - * React to link management on powered pipelines by updating the use count of - * all entities in the source and sink sides of the link. Entities are powered - * on or off accordingly. - * - * Return 0 on success or a negative error code on failure. Powering entities - * off is assumed to never fail. This function will not fail for disconnection - * events. - */ -static int iss_pipeline_link_notify(struct media_link *link, u32 flags, - unsigned int notification) -{ - struct media_entity_graph *graph = - &container_of(link->graph_obj.mdev, struct iss_device, - media_dev)->pm_count_graph; - struct media_entity *source = link->source->entity; - struct media_entity *sink = link->sink->entity; - int source_use; - int sink_use; - int ret; - - if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH) { - ret = media_entity_graph_walk_init(graph, - link->graph_obj.mdev); - if (ret) - return ret; - } - - source_use = iss_pipeline_pm_use_count(source, graph); - sink_use = iss_pipeline_pm_use_count(sink, graph); - - if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && - !(flags & MEDIA_LNK_FL_ENABLED)) { - /* Powering off entities is assumed to never fail. */ - iss_pipeline_pm_power(source, -sink_use, graph); - iss_pipeline_pm_power(sink, -source_use, graph); - return 0; - } - - if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH && - (flags & MEDIA_LNK_FL_ENABLED)) { - ret = iss_pipeline_pm_power(source, sink_use, graph); - if (ret < 0) - return ret; - - ret = iss_pipeline_pm_power(sink, source_use, graph); - if (ret < 0) - iss_pipeline_pm_power(source, -sink_use, graph); - } - - if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH) - media_entity_graph_walk_cleanup(graph); - - return ret; -} - /* ----------------------------------------------------------------------------- * Pipeline stream management */ @@ -1197,7 +988,7 @@ static int iss_register_entities(struct iss_device *iss) strlcpy(iss->media_dev.model, "TI OMAP4 ISS", sizeof(iss->media_dev.model)); iss->media_dev.hw_revision = iss->revision; - iss->media_dev.link_notify = iss_pipeline_link_notify; + iss->media_dev.link_notify = v4l2_pipeline_link_notify; ret = media_device_register(&iss->media_dev); if (ret < 0) { dev_err(iss->dev, "Media device registration failed (%d)\n", @@ -1527,8 +1318,6 @@ error_modules: error_iss: omap4iss_put(iss); error: - platform_set_drvdata(pdev, NULL); - mutex_destroy(&iss->iss_mutex); return ret; diff --git a/drivers/staging/media/omap4iss/iss.h b/drivers/staging/media/omap4iss/iss.h index 05f08a3ca..760ee27da 100644 --- a/drivers/staging/media/omap4iss/iss.h +++ b/drivers/staging/media/omap4iss/iss.h @@ -15,6 +15,8 @@ #define _OMAP4_ISS_H_ #include +#include + #include #include #include @@ -87,7 +89,6 @@ struct iss_reg { struct iss_device { struct v4l2_device v4l2_dev; struct media_device media_dev; - struct media_entity_graph pm_count_graph; struct device *dev; u32 revision; @@ -152,9 +153,6 @@ void omap4iss_isp_subclk_enable(struct iss_device *iss, void omap4iss_isp_subclk_disable(struct iss_device *iss, enum iss_isp_subclk_resource res); -int omap4iss_pipeline_pm_use(struct media_entity *entity, int use, - struct media_entity_graph *graph); - int omap4iss_register_entities(struct platform_device *pdev, struct v4l2_device *v4l2_dev); void omap4iss_unregister_entities(struct platform_device *pdev); diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c index 058233a9d..f54349bce 100644 --- a/drivers/staging/media/omap4iss/iss_video.c +++ b/drivers/staging/media/omap4iss/iss_video.c @@ -19,8 +19,10 @@ #include #include #include + #include #include +#include #include "iss_video.h" #include "iss.h" @@ -1009,13 +1011,7 @@ static int iss_video_open(struct file *file) goto done; } - ret = media_entity_graph_walk_init(&handle->graph, - &video->iss->media_dev); - if (ret) - goto done; - - ret = omap4iss_pipeline_pm_use(&video->video.entity, 1, - &handle->graph); + ret = v4l2_pipeline_pm_use(&video->video.entity, 1); if (ret < 0) { omap4iss_put(video->iss); goto done; @@ -1054,7 +1050,6 @@ static int iss_video_open(struct file *file) done: if (ret < 0) { v4l2_fh_del(&handle->vfh); - media_entity_graph_walk_cleanup(&handle->graph); kfree(handle); } @@ -1070,13 +1065,11 @@ static int iss_video_release(struct file *file) /* Disable streaming and free the buffers queue resources. */ iss_video_streamoff(file, vfh, video->type); - omap4iss_pipeline_pm_use(&video->video.entity, 0, &handle->graph); + v4l2_pipeline_pm_use(&video->video.entity, 0); /* Release the videobuf2 queue */ vb2_queue_release(&handle->queue); - /* Release the file handle. */ - media_entity_graph_walk_cleanup(&handle->graph); v4l2_fh_del(vfh); kfree(handle); file->private_data = NULL; diff --git a/drivers/staging/media/omap4iss/iss_video.h b/drivers/staging/media/omap4iss/iss_video.h index 34588b717..c8bd2958a 100644 --- a/drivers/staging/media/omap4iss/iss_video.h +++ b/drivers/staging/media/omap4iss/iss_video.h @@ -183,7 +183,6 @@ struct iss_video_fh { struct vb2_queue queue; struct v4l2_format format; struct v4l2_fract timeperframe; - struct media_entity_graph graph; }; #define to_iss_video_fh(fh) container_of(fh, struct iss_video_fh, vfh) diff --git a/drivers/staging/media/timb/Kconfig b/drivers/staging/media/timb/Kconfig new file mode 100644 index 000000000..e413fecc1 --- /dev/null +++ b/drivers/staging/media/timb/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_TIMBERDALE + tristate "Support for timberdale Video In/LogiWIN" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && HAS_DMA + depends on (MFD_TIMBERDALE && TIMB_DMA) || COMPILE_TEST + select VIDEO_ADV7180 + select VIDEOBUF_DMA_CONTIG + ---help--- + Add support for the Video In peripherial of the timberdale FPGA. + + This driver is deprecated and will be removed soon unless someone + will start the work to convert this driver to the vb2 framework. diff --git a/drivers/staging/media/timb/Makefile b/drivers/staging/media/timb/Makefile new file mode 100644 index 000000000..4c989c23a --- /dev/null +++ b/drivers/staging/media/timb/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_TIMBERDALE) += timblogiw.o diff --git a/drivers/staging/media/timb/timblogiw.c b/drivers/staging/media/timb/timblogiw.c new file mode 100644 index 000000000..113c9f3c0 --- /dev/null +++ b/drivers/staging/media/timb/timblogiw.c @@ -0,0 +1,870 @@ +/* + * timblogiw.c timberdale FPGA LogiWin Video In driver + * Copyright (c) 2009-2010 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Timberdale FPGA LogiWin Video In + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "timb-video" + +#define TIMBLOGIWIN_NAME "Timberdale Video-In" +#define TIMBLOGIW_VERSION_CODE 0x04 + +#define TIMBLOGIW_LINES_PER_DESC 44 +#define TIMBLOGIW_MAX_VIDEO_MEM 16 + +#define TIMBLOGIW_HAS_DECODER(lw) (lw->pdata.encoder.module_name) + + +struct timblogiw { + struct video_device video_dev; + struct v4l2_device v4l2_dev; /* mutual exclusion */ + struct mutex lock; + struct device *dev; + struct timb_video_platform_data pdata; + struct v4l2_subdev *sd_enc; /* encoder */ + bool opened; +}; + +struct timblogiw_tvnorm { + v4l2_std_id std; + u16 width; + u16 height; + u8 fps; +}; + +struct timblogiw_fh { + struct videobuf_queue vb_vidq; + struct timblogiw_tvnorm const *cur_norm; + struct list_head capture; + struct dma_chan *chan; + spinlock_t queue_lock; /* mutual exclusion */ + unsigned int frame_count; +}; + +struct timblogiw_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + struct scatterlist sg[16]; + dma_cookie_t cookie; + struct timblogiw_fh *fh; +}; + +static const struct timblogiw_tvnorm timblogiw_tvnorms[] = { + { + .std = V4L2_STD_PAL, + .width = 720, + .height = 576, + .fps = 25 + }, + { + .std = V4L2_STD_NTSC, + .width = 720, + .height = 480, + .fps = 30 + } +}; + +static int timblogiw_bytes_per_line(const struct timblogiw_tvnorm *norm) +{ + return norm->width * 2; +} + + +static int timblogiw_frame_size(const struct timblogiw_tvnorm *norm) +{ + return norm->height * timblogiw_bytes_per_line(norm); +} + +static const struct timblogiw_tvnorm *timblogiw_get_norm(const v4l2_std_id std) +{ + int i; + for (i = 0; i < ARRAY_SIZE(timblogiw_tvnorms); i++) + if (timblogiw_tvnorms[i].std & std) + return timblogiw_tvnorms + i; + + /* default to first element */ + return timblogiw_tvnorms; +} + +static void timblogiw_dma_cb(void *data) +{ + struct timblogiw_buffer *buf = data; + struct timblogiw_fh *fh = buf->fh; + struct videobuf_buffer *vb = &buf->vb; + + spin_lock(&fh->queue_lock); + + /* mark the transfer done */ + buf->cookie = -1; + + fh->frame_count++; + + if (vb->state != VIDEOBUF_ERROR) { + list_del(&vb->queue); + v4l2_get_timestamp(&vb->ts); + vb->field_count = fh->frame_count * 2; + vb->state = VIDEOBUF_DONE; + + wake_up(&vb->done); + } + + if (!list_empty(&fh->capture)) { + vb = list_entry(fh->capture.next, struct videobuf_buffer, + queue); + vb->state = VIDEOBUF_ACTIVE; + } + + spin_unlock(&fh->queue_lock); +} + +static bool timblogiw_dma_filter_fn(struct dma_chan *chan, void *filter_param) +{ + return chan->chan_id == (uintptr_t)filter_param; +} + +/* IOCTL functions */ + +static int timblogiw_g_fmt(struct file *file, void *priv, + struct v4l2_format *format) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw *lw = video_get_drvdata(vdev); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s entry\n", __func__); + + if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&lw->lock); + + format->fmt.pix.width = fh->cur_norm->width; + format->fmt.pix.height = fh->cur_norm->height; + format->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + format->fmt.pix.bytesperline = timblogiw_bytes_per_line(fh->cur_norm); + format->fmt.pix.sizeimage = timblogiw_frame_size(fh->cur_norm); + format->fmt.pix.field = V4L2_FIELD_NONE; + + mutex_unlock(&lw->lock); + + return 0; +} + +static int timblogiw_try_fmt(struct file *file, void *priv, + struct v4l2_format *format) +{ + struct video_device *vdev = video_devdata(file); + struct v4l2_pix_format *pix = &format->fmt.pix; + + dev_dbg(&vdev->dev, + "%s - width=%d, height=%d, pixelformat=%d, field=%d\n" + "bytes per line %d, size image: %d, colorspace: %d\n", + __func__, + pix->width, pix->height, pix->pixelformat, pix->field, + pix->bytesperline, pix->sizeimage, pix->colorspace); + + if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (pix->field != V4L2_FIELD_NONE) + return -EINVAL; + + if (pix->pixelformat != V4L2_PIX_FMT_UYVY) + return -EINVAL; + + return 0; +} + +static int timblogiw_s_fmt(struct file *file, void *priv, + struct v4l2_format *format) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw *lw = video_get_drvdata(vdev); + struct timblogiw_fh *fh = priv; + struct v4l2_pix_format *pix = &format->fmt.pix; + int err; + + mutex_lock(&lw->lock); + + err = timblogiw_try_fmt(file, priv, format); + if (err) + goto out; + + if (videobuf_queue_is_busy(&fh->vb_vidq)) { + dev_err(&vdev->dev, "%s queue busy\n", __func__); + err = -EBUSY; + goto out; + } + + pix->width = fh->cur_norm->width; + pix->height = fh->cur_norm->height; + +out: + mutex_unlock(&lw->lock); + return err; +} + +static int timblogiw_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + dev_dbg(&vdev->dev, "%s: Entry\n", __func__); + strncpy(cap->card, TIMBLOGIWIN_NAME, sizeof(cap->card)-1); + strncpy(cap->driver, DRIVER_NAME, sizeof(cap->driver) - 1); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", vdev->name); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int timblogiw_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct video_device *vdev = video_devdata(file); + + dev_dbg(&vdev->dev, "%s, index: %d\n", __func__, fmt->index); + + if (fmt->index != 0) + return -EINVAL; + memset(fmt, 0, sizeof(*fmt)); + fmt->index = 0; + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + strncpy(fmt->description, "4:2:2, packed, YUYV", + sizeof(fmt->description)-1); + fmt->pixelformat = V4L2_PIX_FMT_UYVY; + + return 0; +} + +static int timblogiw_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct timblogiw_fh *fh = priv; + struct v4l2_captureparm *cp = &sp->parm.capture; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = fh->cur_norm->fps; + + return 0; +} + +static int timblogiw_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_reqbufs(&fh->vb_vidq, rb); +} + +static int timblogiw_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_querybuf(&fh->vb_vidq, b); +} + +static int timblogiw_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_qbuf(&fh->vb_vidq, b); +} + +static int timblogiw_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags & O_NONBLOCK); +} + +static int timblogiw_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + *std = fh->cur_norm->std; + return 0; +} + +static int timblogiw_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw *lw = video_get_drvdata(vdev); + struct timblogiw_fh *fh = priv; + int err = 0; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + mutex_lock(&lw->lock); + + if (TIMBLOGIW_HAS_DECODER(lw)) + err = v4l2_subdev_call(lw->sd_enc, video, s_std, std); + + if (!err) + fh->cur_norm = timblogiw_get_norm(std); + + mutex_unlock(&lw->lock); + + return err; +} + +static int timblogiw_enuminput(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct video_device *vdev = video_devdata(file); + int i; + + dev_dbg(&vdev->dev, "%s: Entry\n", __func__); + + if (inp->index != 0) + return -EINVAL; + + inp->index = 0; + + strncpy(inp->name, "Timb input 1", sizeof(inp->name) - 1); + inp->type = V4L2_INPUT_TYPE_CAMERA; + + inp->std = 0; + for (i = 0; i < ARRAY_SIZE(timblogiw_tvnorms); i++) + inp->std |= timblogiw_tvnorms[i].std; + + return 0; +} + +static int timblogiw_g_input(struct file *file, void *priv, + unsigned int *input) +{ + struct video_device *vdev = video_devdata(file); + + dev_dbg(&vdev->dev, "%s: Entry\n", __func__); + + *input = 0; + + return 0; +} + +static int timblogiw_s_input(struct file *file, void *priv, unsigned int input) +{ + struct video_device *vdev = video_devdata(file); + + dev_dbg(&vdev->dev, "%s: Entry\n", __func__); + + if (input != 0) + return -EINVAL; + return 0; +} + +static int timblogiw_streamon(struct file *file, void *priv, enum v4l2_buf_type type) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dev_dbg(&vdev->dev, "%s - No capture device\n", __func__); + return -EINVAL; + } + + fh->frame_count = 0; + return videobuf_streamon(&fh->vb_vidq); +} + +static int timblogiw_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s entry\n", __func__); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamoff(&fh->vb_vidq); +} + +static int timblogiw_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw *lw = video_get_drvdata(vdev); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s entry\n", __func__); + + if (TIMBLOGIW_HAS_DECODER(lw)) + return v4l2_subdev_call(lw->sd_enc, video, querystd, std); + else { + *std = fh->cur_norm->std; + return 0; + } +} + +static int timblogiw_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = priv; + + dev_dbg(&vdev->dev, "%s - index: %d, format: %d\n", __func__, + fsize->index, fsize->pixel_format); + + if ((fsize->index != 0) || + (fsize->pixel_format != V4L2_PIX_FMT_UYVY)) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fh->cur_norm->width; + fsize->discrete.height = fh->cur_norm->height; + + return 0; +} + +/* Video buffer functions */ + +static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct timblogiw_fh *fh = vq->priv_data; + + *size = timblogiw_frame_size(fh->cur_norm); + + if (!*count) + *count = 32; + + while (*size * *count > TIMBLOGIW_MAX_VIDEO_MEM * 1024 * 1024) + (*count)--; + + return 0; +} + +static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct timblogiw_fh *fh = vq->priv_data; + struct timblogiw_buffer *buf = container_of(vb, struct timblogiw_buffer, + vb); + unsigned int data_size = timblogiw_frame_size(fh->cur_norm); + int err = 0; + + if (vb->baddr && vb->bsize < data_size) + /* User provided buffer, but it is too small */ + return -ENOMEM; + + vb->size = data_size; + vb->width = fh->cur_norm->width; + vb->height = fh->cur_norm->height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + int i; + unsigned int size; + unsigned int bytes_per_desc = TIMBLOGIW_LINES_PER_DESC * + timblogiw_bytes_per_line(fh->cur_norm); + dma_addr_t addr; + + sg_init_table(buf->sg, ARRAY_SIZE(buf->sg)); + + err = videobuf_iolock(vq, vb, NULL); + if (err) + goto err; + + addr = videobuf_to_dma_contig(vb); + for (i = 0, size = 0; size < data_size; i++) { + sg_dma_address(buf->sg + i) = addr + size; + size += bytes_per_desc; + sg_dma_len(buf->sg + i) = (size > data_size) ? + (bytes_per_desc - (size - data_size)) : + bytes_per_desc; + } + + vb->state = VIDEOBUF_PREPARED; + buf->cookie = -1; + buf->fh = fh; + } + + return 0; + +err: + videobuf_dma_contig_free(vq, vb); + vb->state = VIDEOBUF_NEEDS_INIT; + return err; +} + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct timblogiw_fh *fh = vq->priv_data; + struct timblogiw_buffer *buf = container_of(vb, struct timblogiw_buffer, + vb); + struct dma_async_tx_descriptor *desc; + int sg_elems; + int bytes_per_desc = TIMBLOGIW_LINES_PER_DESC * + timblogiw_bytes_per_line(fh->cur_norm); + + sg_elems = timblogiw_frame_size(fh->cur_norm) / bytes_per_desc; + sg_elems += + (timblogiw_frame_size(fh->cur_norm) % bytes_per_desc) ? 1 : 0; + + if (list_empty(&fh->capture)) + vb->state = VIDEOBUF_ACTIVE; + else + vb->state = VIDEOBUF_QUEUED; + + list_add_tail(&vb->queue, &fh->capture); + + spin_unlock_irq(&fh->queue_lock); + + desc = dmaengine_prep_slave_sg(fh->chan, + buf->sg, sg_elems, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) { + spin_lock_irq(&fh->queue_lock); + list_del_init(&vb->queue); + vb->state = VIDEOBUF_PREPARED; + return; + } + + desc->callback_param = buf; + desc->callback = timblogiw_dma_cb; + + buf->cookie = desc->tx_submit(desc); + + spin_lock_irq(&fh->queue_lock); +} + +static void buffer_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct timblogiw_fh *fh = vq->priv_data; + struct timblogiw_buffer *buf = container_of(vb, struct timblogiw_buffer, + vb); + + videobuf_waiton(vq, vb, 0, 0); + if (buf->cookie >= 0) + dma_sync_wait(fh->chan, buf->cookie); + + videobuf_dma_contig_free(vq, vb); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops timblogiw_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* Device Operations functions */ + +static int timblogiw_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw *lw = video_get_drvdata(vdev); + struct timblogiw_fh *fh; + v4l2_std_id std; + dma_cap_mask_t mask; + int err = 0; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + mutex_lock(&lw->lock); + if (lw->opened) { + err = -EBUSY; + goto out; + } + + if (TIMBLOGIW_HAS_DECODER(lw) && !lw->sd_enc) { + struct i2c_adapter *adapt; + + /* find the video decoder */ + adapt = i2c_get_adapter(lw->pdata.i2c_adapter); + if (!adapt) { + dev_err(&vdev->dev, "No I2C bus #%d\n", + lw->pdata.i2c_adapter); + err = -ENODEV; + goto out; + } + + /* now find the encoder */ + lw->sd_enc = v4l2_i2c_new_subdev_board(&lw->v4l2_dev, adapt, + lw->pdata.encoder.info, NULL); + + i2c_put_adapter(adapt); + + if (!lw->sd_enc) { + dev_err(&vdev->dev, "Failed to get encoder: %s\n", + lw->pdata.encoder.module_name); + err = -ENODEV; + goto out; + } + } + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) { + err = -ENOMEM; + goto out; + } + + fh->cur_norm = timblogiw_tvnorms; + timblogiw_querystd(file, fh, &std); + fh->cur_norm = timblogiw_get_norm(std); + + INIT_LIST_HEAD(&fh->capture); + spin_lock_init(&fh->queue_lock); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + + /* find the DMA channel */ + fh->chan = dma_request_channel(mask, timblogiw_dma_filter_fn, + (void *)(uintptr_t)lw->pdata.dma_channel); + if (!fh->chan) { + dev_err(&vdev->dev, "Failed to get DMA channel\n"); + kfree(fh); + err = -ENODEV; + goto out; + } + + file->private_data = fh; + videobuf_queue_dma_contig_init(&fh->vb_vidq, + &timblogiw_video_qops, lw->dev, &fh->queue_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, + sizeof(struct timblogiw_buffer), fh, NULL); + + lw->opened = true; +out: + mutex_unlock(&lw->lock); + + return err; +} + +static int timblogiw_close(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw *lw = video_get_drvdata(vdev); + struct timblogiw_fh *fh = file->private_data; + + dev_dbg(&vdev->dev, "%s: Entry\n", __func__); + + videobuf_stop(&fh->vb_vidq); + videobuf_mmap_free(&fh->vb_vidq); + + dma_release_channel(fh->chan); + + kfree(fh); + + mutex_lock(&lw->lock); + lw->opened = false; + mutex_unlock(&lw->lock); + return 0; +} + +static ssize_t timblogiw_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = file->private_data; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_read_stream(&fh->vb_vidq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static unsigned int timblogiw_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = file->private_data; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_poll_stream(file, &fh->vb_vidq, wait); +} + +static int timblogiw_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + struct timblogiw_fh *fh = file->private_data; + + dev_dbg(&vdev->dev, "%s: entry\n", __func__); + + return videobuf_mmap_mapper(&fh->vb_vidq, vma); +} + +/* Platform device functions */ + +static struct v4l2_ioctl_ops timblogiw_ioctl_ops = { + .vidioc_querycap = timblogiw_querycap, + .vidioc_enum_fmt_vid_cap = timblogiw_enum_fmt, + .vidioc_g_fmt_vid_cap = timblogiw_g_fmt, + .vidioc_try_fmt_vid_cap = timblogiw_try_fmt, + .vidioc_s_fmt_vid_cap = timblogiw_s_fmt, + .vidioc_g_parm = timblogiw_g_parm, + .vidioc_reqbufs = timblogiw_reqbufs, + .vidioc_querybuf = timblogiw_querybuf, + .vidioc_qbuf = timblogiw_qbuf, + .vidioc_dqbuf = timblogiw_dqbuf, + .vidioc_g_std = timblogiw_g_std, + .vidioc_s_std = timblogiw_s_std, + .vidioc_enum_input = timblogiw_enuminput, + .vidioc_g_input = timblogiw_g_input, + .vidioc_s_input = timblogiw_s_input, + .vidioc_streamon = timblogiw_streamon, + .vidioc_streamoff = timblogiw_streamoff, + .vidioc_querystd = timblogiw_querystd, + .vidioc_enum_framesizes = timblogiw_enum_framesizes, +}; + +static struct v4l2_file_operations timblogiw_fops = { + .owner = THIS_MODULE, + .open = timblogiw_open, + .release = timblogiw_close, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .mmap = timblogiw_mmap, + .read = timblogiw_read, + .poll = timblogiw_poll, +}; + +static struct video_device timblogiw_template = { + .name = TIMBLOGIWIN_NAME, + .fops = &timblogiw_fops, + .ioctl_ops = &timblogiw_ioctl_ops, + .release = video_device_release_empty, + .minor = -1, + .tvnorms = V4L2_STD_PAL | V4L2_STD_NTSC +}; + +static int timblogiw_probe(struct platform_device *pdev) +{ + int err; + struct timblogiw *lw = NULL; + struct timb_video_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) { + dev_err(&pdev->dev, "No platform data\n"); + err = -EINVAL; + goto err; + } + + if (!pdata->encoder.module_name) + dev_info(&pdev->dev, "Running without decoder\n"); + + lw = devm_kzalloc(&pdev->dev, sizeof(*lw), GFP_KERNEL); + if (!lw) { + err = -ENOMEM; + goto err; + } + + if (pdev->dev.parent) + lw->dev = pdev->dev.parent; + else + lw->dev = &pdev->dev; + + memcpy(&lw->pdata, pdata, sizeof(lw->pdata)); + + mutex_init(&lw->lock); + + lw->video_dev = timblogiw_template; + + strlcpy(lw->v4l2_dev.name, DRIVER_NAME, sizeof(lw->v4l2_dev.name)); + err = v4l2_device_register(NULL, &lw->v4l2_dev); + if (err) + goto err; + + lw->video_dev.v4l2_dev = &lw->v4l2_dev; + + platform_set_drvdata(pdev, lw); + video_set_drvdata(&lw->video_dev, lw); + + err = video_register_device(&lw->video_dev, VFL_TYPE_GRABBER, 0); + if (err) { + dev_err(&pdev->dev, "Error reg video: %d\n", err); + goto err_request; + } + + return 0; + +err_request: + v4l2_device_unregister(&lw->v4l2_dev); +err: + dev_err(&pdev->dev, "Failed to register: %d\n", err); + + return err; +} + +static int timblogiw_remove(struct platform_device *pdev) +{ + struct timblogiw *lw = platform_get_drvdata(pdev); + + video_unregister_device(&lw->video_dev); + + v4l2_device_unregister(&lw->v4l2_dev); + + return 0; +} + +static struct platform_driver timblogiw_platform_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = timblogiw_probe, + .remove = timblogiw_remove, +}; + +module_platform_driver(timblogiw_platform_driver); + +MODULE_DESCRIPTION(TIMBLOGIWIN_NAME); +MODULE_AUTHOR("Pelagicore AB "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:"DRIVER_NAME); -- cgit v1.2.3-54-g00ecf