static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd) { struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); struct sh_mobile_ceu_dev *pcdev = ici->priv; unsigned long flags; BUG_ON(icd != pcdev->icd); ceu_write(pcdev, CEIER, 0); ceu_write(pcdev, CAPSR, 1 << 16); spin_lock_irqsave(&pcdev->lock, flags); if (pcdev->active) { list_del(&pcdev->active->queue); pcdev->active->state = VIDEOBUF_ERROR; wake_up_all(&pcdev->active->done); pcdev->active = NULL; } spin_unlock_irqrestore(&pcdev->lock, flags); pm_runtime_put_sync(ici->v4l2_dev.dev); dev_info(icd->dev.parent, "SuperH Mobile CEU driver detached from camera %d\n", icd->devnum); pcdev->icd = NULL; }
static void sh_mobile_ceu_videobuf_release(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->dev.parent); struct sh_mobile_ceu_dev *pcdev = ici->priv; unsigned long flags; spin_lock_irqsave(&pcdev->lock, flags); if (pcdev->active == vb) { ceu_write(pcdev, CAPSR, 1 << 16); pcdev->active = NULL; } if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED) && !list_empty(&vb->queue)) { vb->state = VIDEOBUF_ERROR; list_del_init(&vb->queue); } spin_unlock_irqrestore(&pcdev->lock, flags); free_buffer(vq, container_of(vb, struct sh_mobile_ceu_buffer, vb)); }
static void sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev) { struct soc_camera_device *icd = pcdev->icd; dma_addr_t phys_addr_top, phys_addr_bottom; /* The hardware is _very_ picky about this sequence. Especially * the CEU_CETCR_MAGIC value. It seems like we need to acknowledge * several not-so-well documented interrupt sources in CETCR. */ ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~CEU_CEIER_CPEIE); ceu_write(pcdev, CETCR, ~ceu_read(pcdev, CETCR) & CEU_CETCR_MAGIC); ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_CPEIE); ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~CEU_CAPCR_CTNCP); ceu_write(pcdev, CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW); if (!pcdev->active) return; phys_addr_top = videobuf_to_dma_contig(pcdev->active); ceu_write(pcdev, CDAYR, phys_addr_top); if (pcdev->is_interlaced) { phys_addr_bottom = phys_addr_top + icd->width; ceu_write(pcdev, CDBYR, phys_addr_bottom); } switch (icd->current_fmt->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61: phys_addr_top += icd->width * icd->height; ceu_write(pcdev, CDACR, phys_addr_top); if (pcdev->is_interlaced) { phys_addr_bottom = phys_addr_top + icd->width; ceu_write(pcdev, CDBCR, phys_addr_bottom); } } pcdev->active->state = VIDEOBUF_ACTIVE; ceu_write(pcdev, CAPSR, 0x1); /* start capture */ }
static void sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev) { struct soc_camera_device *icd = pcdev->icd; dma_addr_t phys_addr_top, phys_addr_bottom; ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~CEU_CEIER_CPEIE); ceu_write(pcdev, CETCR, ~ceu_read(pcdev, CETCR) & CEU_CETCR_MAGIC); ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_CPEIE); ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~CEU_CAPCR_CTNCP); ceu_write(pcdev, CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW); if (!pcdev->active) return; phys_addr_top = videobuf_to_dma_contig(pcdev->active); ceu_write(pcdev, CDAYR, phys_addr_top); if (pcdev->is_interlaced) { phys_addr_bottom = phys_addr_top + icd->user_width; ceu_write(pcdev, CDBYR, phys_addr_bottom); } switch (icd->current_fmt->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61: phys_addr_top += icd->user_width * icd->user_height; ceu_write(pcdev, CDACR, phys_addr_top); if (pcdev->is_interlaced) { phys_addr_bottom = phys_addr_top + icd->user_width; ceu_write(pcdev, CDBCR, phys_addr_bottom); } } pcdev->active->state = VIDEOBUF_ACTIVE; ceu_write(pcdev, CAPSR, 0x1); }
static void sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev) { ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~1); ceu_write(pcdev, CETCR, ~ceu_read(pcdev, CETCR) & 0x0317f313); ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | 1); ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~0x10000); ceu_write(pcdev, CETCR, 0x0317f313 ^ 0x10); if (pcdev->active) { pcdev->active->state = VIDEOBUF_ACTIVE; ceu_write(pcdev, CDAYR, videobuf_to_dma_contig(pcdev->active)); ceu_write(pcdev, CAPSR, 0x1); /* start capture */ } }
static int sh_mobile_ceu_set_ctrl(struct soc_camera_device *icd, struct v4l2_control *ctrl) { struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); struct sh_mobile_ceu_dev *pcdev = ici->priv; switch (ctrl->id) { case V4L2_CID_SHARPNESS: switch (icd->current_fmt->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61: ceu_write(pcdev, CLFCR, !ctrl->value); return 0; } return -EINVAL; } return -ENOIOCTLCMD; }
static void capture_restore(struct sh_mobile_ceu_dev *pcdev, u32 capsr) { unsigned long timeout = jiffies + 10 * HZ; while ((ceu_read(pcdev, CSTSR) & 1) && time_before(jiffies, timeout)) msleep(1); if (time_after(jiffies, timeout)) { dev_err(pcdev->ici.v4l2_dev.dev, "Timeout waiting for frame end! Interface problem?\n"); return; } while (ceu_read(pcdev, CAPSR) & (1 << 16)) udelay(10); if (capsr & ~(1 << 16)) ceu_write(pcdev, CAPSR, capsr); }
static int sh_mobile_ceu_add_device(struct soc_camera_device *icd) { struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); struct sh_mobile_ceu_dev *pcdev = ici->priv; if (pcdev->icd) return -EBUSY; dev_info(icd->dev.parent, "SuperH Mobile CEU driver attached to camera %d\n", icd->devnum); pm_runtime_get_sync(ici->v4l2_dev.dev); ceu_write(pcdev, CAPSR, 1 << 16); while (ceu_read(pcdev, CSTSR) & 1) msleep(1); pcdev->icd = icd; return 0; }
static int sh_mobile_ceu_set_bus_param(struct soc_camera_device *icd, __u32 pixfmt) { struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); struct sh_mobile_ceu_dev *pcdev = ici->priv; int ret; unsigned long camera_flags, common_flags, value; int yuv_lineskip; struct sh_mobile_ceu_cam *cam = icd->host_priv; u32 capsr = capture_save_reset(pcdev); camera_flags = icd->ops->query_bus_param(icd); common_flags = soc_camera_bus_param_compatible(camera_flags, make_bus_param(pcdev)); if (!common_flags) return -EINVAL; ret = icd->ops->set_bus_param(icd, common_flags); if (ret < 0) return ret; switch (common_flags & SOCAM_DATAWIDTH_MASK) { case SOCAM_DATAWIDTH_8: pcdev->is_16bit = 0; break; case SOCAM_DATAWIDTH_16: pcdev->is_16bit = 1; break; default: return -EINVAL; } ceu_write(pcdev, CRCNTR, 0); ceu_write(pcdev, CRCMPR, 0); value = 0x00000010; yuv_lineskip = 0; switch (icd->current_fmt->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: yuv_lineskip = 1; case V4L2_PIX_FMT_NV16: case V4L2_PIX_FMT_NV61: switch (cam->camera_fmt->fourcc) { case V4L2_PIX_FMT_UYVY: value = 0x00000000; break; case V4L2_PIX_FMT_VYUY: value = 0x00000100; break; case V4L2_PIX_FMT_YUYV: value = 0x00000200; break; case V4L2_PIX_FMT_YVYU: value = 0x00000300; break; default: BUG(); } } if (icd->current_fmt->fourcc == V4L2_PIX_FMT_NV21 || icd->current_fmt->fourcc == V4L2_PIX_FMT_NV61) value ^= 0x00000100; value |= common_flags & SOCAM_VSYNC_ACTIVE_LOW ? 1 << 1 : 0; value |= common_flags & SOCAM_HSYNC_ACTIVE_LOW ? 1 << 0 : 0; value |= pcdev->is_16bit ? 1 << 12 : 0; ceu_write(pcdev, CAMCR, value); ceu_write(pcdev, CAPCR, 0x00300000); ceu_write(pcdev, CAIFR, pcdev->is_interlaced ? 0x101 : 0); sh_mobile_ceu_set_rect(icd, icd->user_width, icd->user_height); mdelay(1); ceu_write(pcdev, CFLCR, pcdev->cflcr); value = 0x00000017; if (yuv_lineskip) value &= ~0x00000010; ceu_write(pcdev, CDOCR, value); ceu_write(pcdev, CFWCR, 0); dev_dbg(icd->dev.parent, "S_FMT successful for %c%c%c%c %ux%u\n", pixfmt & 0xff, (pixfmt >> 8) & 0xff, (pixfmt >> 16) & 0xff, (pixfmt >> 24) & 0xff, icd->user_width, icd->user_height); capture_restore(pcdev, capsr); return 0; }
static u32 capture_save_reset(struct sh_mobile_ceu_dev *pcdev) { u32 capsr = ceu_read(pcdev, CAPSR); ceu_write(pcdev, CAPSR, 1 << 16); return capsr; }
static void sh_mobile_ceu_set_rect(struct soc_camera_device *icd, unsigned int out_width, unsigned int out_height) { struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); struct sh_mobile_ceu_cam *cam = icd->host_priv; struct v4l2_rect *rect = &cam->ceu_rect; struct sh_mobile_ceu_dev *pcdev = ici->priv; unsigned int height, width, cdwdr_width, in_width, in_height; unsigned int left_offset, top_offset; u32 camor; dev_dbg(icd->dev.parent, "Crop %ux%u@%u:%u\n", rect->width, rect->height, rect->left, rect->top); left_offset = rect->left; top_offset = rect->top; if (pcdev->image_mode) { in_width = rect->width; if (!pcdev->is_16bit) { in_width *= 2; left_offset *= 2; } width = cdwdr_width = out_width; } else { unsigned int w_factor = (icd->current_fmt->depth + 7) >> 3; width = out_width * w_factor / 2; if (!pcdev->is_16bit) w_factor *= 2; in_width = rect->width * w_factor / 2; left_offset = left_offset * w_factor / 2; cdwdr_width = width * 2; } height = out_height; in_height = rect->height; if (pcdev->is_interlaced) { height /= 2; in_height /= 2; top_offset /= 2; cdwdr_width *= 2; } camor = left_offset | (top_offset << 16); dev_geo(icd->dev.parent, "CAMOR 0x%x, CAPWR 0x%x, CFSZR 0x%x, CDWDR 0x%x\n", camor, (in_height << 16) | in_width, (height << 16) | width, cdwdr_width); ceu_write(pcdev, CAMOR, camor); ceu_write(pcdev, CAPWR, (in_height << 16) | in_width); ceu_write(pcdev, CFSZR, (height << 16) | width); ceu_write(pcdev, CDWDR, cdwdr_width); }
static int sh_mobile_ceu_set_crop(struct soc_camera_device *icd, struct v4l2_crop *a) { struct v4l2_rect *rect = &a->c; struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); struct sh_mobile_ceu_dev *pcdev = ici->priv; struct v4l2_crop cam_crop; struct sh_mobile_ceu_cam *cam = icd->host_priv; struct v4l2_rect *cam_rect = &cam_crop.c, *ceu_rect = &cam->ceu_rect; struct v4l2_subdev *sd = soc_camera_to_subdev(icd); struct device *dev = icd->dev.parent; struct v4l2_format f; struct v4l2_pix_format *pix = &f.fmt.pix; unsigned int scale_comb_h, scale_comb_v, scale_ceu_h, scale_ceu_v, out_width, out_height; u32 capsr, cflcr; int ret; ret = get_scales(icd, &scale_comb_h, &scale_comb_v); if (ret < 0) return ret; dev_geo(dev, "1: combined scales %u:%u\n", scale_comb_h, scale_comb_v); ret = client_s_crop(sd, a, &cam_crop); if (ret < 0) return ret; dev_geo(dev, "2: camera cropped to %ux%u@%u:%u\n", cam_rect->width, cam_rect->height, cam_rect->left, cam_rect->top); out_width = scale_down(rect->width, scale_comb_h); out_height = scale_down(rect->height, scale_comb_v); if (out_width > 2560) out_width = 2560; else if (out_width < 2) out_width = 2; if (out_height > 1920) out_height = 1920; else if (out_height < 4) out_height = 4; dev_geo(dev, "3: Adjusted output %ux%u\n", out_width, out_height); pix->width = scale_down(cam_rect->width, scale_comb_h); pix->height = scale_down(cam_rect->height, scale_comb_v); dev_geo(dev, "5: camera target %ux%u\n", pix->width, pix->height); pix->pixelformat = cam->camera_fmt->fourcc; pix->colorspace = cam->camera_fmt->colorspace; capsr = capture_save_reset(pcdev); dev_dbg(dev, "CAPSR 0x%x, CFLCR 0x%x\n", capsr, pcdev->cflcr); rect->left -= cam_rect->left; rect->top -= cam_rect->top; f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = client_scale(icd, cam_rect, rect, ceu_rect, &f, pcdev->image_mode && !pcdev->is_interlaced); dev_geo(dev, "6-9: %d\n", ret); sh_mobile_ceu_set_rect(icd, out_width, out_height); dev_geo(dev, "10: CEU cropped to %ux%u@%u:%u\n", ceu_rect->width, ceu_rect->height, ceu_rect->left, ceu_rect->top); scale_ceu_h = calc_scale(ceu_rect->width, &out_width); scale_ceu_v = calc_scale(ceu_rect->height, &out_height); dev_geo(dev, "11: CEU scales %u:%u\n", scale_ceu_h, scale_ceu_v); cflcr = scale_ceu_h | (scale_ceu_v << 16); if (cflcr != pcdev->cflcr) { pcdev->cflcr = cflcr; ceu_write(pcdev, CFLCR, cflcr); } if (pcdev->active) capsr |= 1; capture_restore(pcdev, capsr); icd->user_width = out_width; icd->user_height = out_height; return ret; }