/* * linux-3.10/drivers/media/platform/sunxi-vin/vin-isp/sunxi_isp.c * * Copyright (c) 2007-2017 Allwinnertech Co., Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ /* ****************************************************************************** * * sunxi_isp.c * * Hawkview ISP - sunxi_isp.c module * * Copyright (c) 2014 by Allwinnertech Co., Ltd. http://www.allwinnertech.com * * Version Author Date Description * * 3.0 Yang Feng 2014/12/11 ISP Tuning Tools Support * ****************************************************************************** */ #include #include #include #include #include #include #include #include #include "../platform/platform_cfg.h" #include "sunxi_isp.h" #include "../vin-video/vin_core.h" #include "../utility/vin_io.h" #define ISP_MODULE_NAME "vin_isp" extern unsigned int isp_reparse_flag; static LIST_HEAD(isp_drv_list); #define MIN_IN_WIDTH 32 #define MIN_IN_HEIGHT 32 #define MAX_IN_WIDTH 4095 #define MAX_IN_HEIGHT 4095 static struct isp_pix_fmt sunxi_isp_formats[] = { { .name = "RAW8 (BGGR)", .fourcc = V4L2_PIX_FMT_SBGGR8, .depth = {8}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SBGGR8_1X8, .infmt = ISP_BGGR, }, { .name = "RAW8 (GBRG)", .fourcc = V4L2_PIX_FMT_SGBRG8, .depth = {8}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGBRG8_1X8, .infmt = ISP_GBRG, }, { .name = "RAW8 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG8, .depth = {8}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG8_1X8, .infmt = ISP_GRBG, }, { .name = "RAW8 (RGGB)", .fourcc = V4L2_PIX_FMT_SRGGB8, .depth = {8}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SRGGB8_1X8, .infmt = ISP_RGGB, }, { .name = "RAW10 (BGGR)", .fourcc = V4L2_PIX_FMT_SBGGR10, .depth = {10}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SBGGR10_1X10, .infmt = ISP_BGGR, }, { .name = "RAW10 (GBRG)", .fourcc = V4L2_PIX_FMT_SGBRG8, .depth = {10}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGBRG10_1X10, .infmt = ISP_GBRG, }, { .name = "RAW10 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG10, .depth = {10}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG10_1X10, .infmt = ISP_GRBG, }, { .name = "RAW10 (RGGB)", .fourcc = V4L2_PIX_FMT_SRGGB10, .depth = {10}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SRGGB10_1X10, .infmt = ISP_RGGB, }, { .name = "RAW12 (BGGR)", .fourcc = V4L2_PIX_FMT_SBGGR12, .depth = {12}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SBGGR12_1X12, .infmt = ISP_BGGR, }, { .name = "RAW12 (GBRG)", .fourcc = V4L2_PIX_FMT_SGBRG12, .depth = {12}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGBRG12_1X12, .infmt = ISP_GBRG, }, { .name = "RAW12 (GRBG)", .fourcc = V4L2_PIX_FMT_SGRBG12, .depth = {12}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SGRBG12_1X12, .infmt = ISP_GRBG, }, { .name = "RAW12 (RGGB)", .fourcc = V4L2_PIX_FMT_SRGGB12, .depth = {12}, .color = 0, .memplanes = 1, .mbus_code = V4L2_MBUS_FMT_SRGGB12_1X12, .infmt = ISP_RGGB, }, }; static int isp_3d_pingpong_alloc(struct isp_dev *isp) { int ret = 0; isp->isp_3d_buf.flags[0] = ISP_3D_R; isp->isp_3d_buf.flags[1] = ISP_3D_W; isp->isp_3d_buf.buf[0].size = isp->mf.width * isp->mf.height * 4; isp->isp_3d_buf.buf[1].size = isp->mf.width * isp->mf.height * 4; ret = os_mem_alloc(&isp->pdev->dev, &isp->isp_3d_buf.buf[0]); if (ret < 0) { vin_err("isp 3d pingpong buf0 requset add failed!\n"); return -ENOMEM; } ret = os_mem_alloc(&isp->pdev->dev, &isp->isp_3d_buf.buf[1]); if (ret < 0) { vin_err("isp 3d pingpong buf1 requset add failed!\n"); return -ENOMEM; } return ret; } static void isp_3d_pingpong_free(struct isp_dev *isp) { os_mem_free(&isp->pdev->dev, &isp->isp_3d_buf.buf[0]); os_mem_free(&isp->pdev->dev, &isp->isp_3d_buf.buf[1]); } static int isp_3d_pingpong_update(struct isp_dev *isp, struct isp_hw_pingpong *pingpong) { dma_addr_t addr; int tmp = -1; tmp = pingpong->flags[0]; pingpong->flags[0] = pingpong->flags[1]; pingpong->flags[1] = tmp; addr = (dma_addr_t)pingpong->buf[pingpong->flags[ISP_3D_W]].dma_addr; writel(addr >> 2, isp->isp_load.vir_addr + 0xc8); addr = (dma_addr_t)pingpong->buf[pingpong->flags[ISP_3D_R]].dma_addr; writel(addr >> 2, isp->isp_load.vir_addr + 0xcc); return 0; } static int sunxi_isp_subdev_set_crop(struct v4l2_subdev *sd, const struct v4l2_crop *crop) { struct isp_dev *isp = v4l2_get_subdevdata(sd); isp->isp_ob.ob_valid.width = crop->c.width; isp->isp_ob.ob_valid.height = crop->c.height; isp->isp_ob.ob_start.hor = crop->c.left; isp->isp_ob.ob_start.ver = crop->c.top; isp->isp_ob.ob_black.width = crop->c.width + crop->c.left; isp->isp_ob.ob_black.height = crop->c.height + crop->c.top; return 0; } static int sunxi_isp_subdev_s_power(struct v4l2_subdev *sd, int enable) { struct isp_dev *isp = v4l2_get_subdevdata(sd); if (!isp->use_isp) return 0; return 0; } static int sunxi_isp_subdev_s_stream(struct v4l2_subdev *sd, int enable) { struct isp_dev *isp = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *mf = &isp->mf; struct mbus_framefmt_res *res = (void *)mf->reserved; struct isp_size_settings *ob = &isp->isp_ob; if (!isp->use_isp) return 0; switch (res->res_pix_fmt) { case V4L2_PIX_FMT_SBGGR8: case V4L2_PIX_FMT_SGBRG8: case V4L2_PIX_FMT_SGRBG8: case V4L2_PIX_FMT_SRGGB8: case V4L2_PIX_FMT_SBGGR10: case V4L2_PIX_FMT_SGBRG10: case V4L2_PIX_FMT_SGRBG10: case V4L2_PIX_FMT_SRGGB10: case V4L2_PIX_FMT_SBGGR12: case V4L2_PIX_FMT_SGBRG12: case V4L2_PIX_FMT_SGRBG12: case V4L2_PIX_FMT_SRGGB12: vin_print("%s output fmt is raw, return directly\n", __func__); return 0; default: break; } if (enable) { isp->h3a_stat.frame_number = 0; if (isp_3d_pingpong_alloc(isp)) return -ENOMEM; isp_3d_pingpong_update(isp, &isp->isp_3d_buf); isp->load_shadow = kzalloc(ISP_LOAD_REG_SIZE, GFP_KERNEL); if (isp->load_shadow == NULL) { vin_err("%s - kzalloc load_shadow failed\n", __func__); return -ENOMEM; } bsp_isp_enable(isp->id); bsp_isp_src0_enable(isp->id); bsp_isp_set_input_fmt(isp->id, isp->isp_fmt->infmt); ob->ob_valid.width = mf->width; ob->ob_valid.height = mf->height; ob->ob_start.hor = 0; ob->ob_start.ver = 0; ob->ob_black.width = mf->width + ob->ob_start.hor; ob->ob_black.height = mf->height + ob->ob_start.ver; bsp_isp_set_ob_zone(isp->id, &isp->isp_ob); sunxi_isp_dump_regs(sd); bsp_isp_set_para_ready(isp->id); bsp_isp_set_speed_mode(isp->id, 3); bsp_isp_channel_enable(isp->id, 0); bsp_isp_clr_irq_status(isp->id, ISP_IRQ_EN_ALL); bsp_isp_irq_enable(isp->id, FINISH_INT_EN | SRC0_FIFO_INT_EN | FRAME_ERROR_INT_EN | FRAME_LOST_INT_EN); bsp_isp_capture_start(isp->id); } else { bsp_isp_capture_stop(isp->id); bsp_isp_src0_disable(isp->id); bsp_isp_channel_disable(isp->id, 0); bsp_isp_irq_disable(isp->id, ISP_IRQ_EN_ALL); bsp_isp_clr_irq_status(isp->id, ISP_IRQ_EN_ALL); kfree(isp->load_shadow); bsp_isp_disable(isp->id); isp_3d_pingpong_free(isp); } vin_print("%s on = %d, %d*%d %x %d\n", __func__, enable, mf->width, mf->height, mf->code, mf->field); return 0; } static struct isp_pix_fmt *__isp_find_format(const u32 *pixelformat, const u32 *mbus_code, int index) { struct isp_pix_fmt *fmt, *def_fmt = NULL; unsigned int i; int id = 0; if (index >= (int)ARRAY_SIZE(sunxi_isp_formats)) return NULL; for (i = 0; i < ARRAY_SIZE(sunxi_isp_formats); ++i) { fmt = &sunxi_isp_formats[i]; if (pixelformat && fmt->fourcc == *pixelformat) return fmt; if (mbus_code && fmt->mbus_code == *mbus_code) return fmt; if (index == id) def_fmt = fmt; id++; } return def_fmt; } static struct v4l2_mbus_framefmt *__isp_get_format(struct isp_dev *isp, struct v4l2_subdev_fh *fh, enum v4l2_subdev_format_whence which) { if (which == V4L2_SUBDEV_FORMAT_TRY) return fh ? v4l2_subdev_get_try_format(fh, 0) : NULL; return &isp->mf; } static struct isp_pix_fmt *__isp_try_format(struct v4l2_mbus_framefmt *mf) { struct isp_pix_fmt *isp_fmt; isp_fmt = __isp_find_format(NULL, &mf->code, 0); if (isp_fmt == NULL) isp_fmt = &sunxi_isp_formats[0]; mf->width = clamp_t(u32, mf->width, MIN_IN_WIDTH, MAX_IN_WIDTH); mf->height = clamp_t(u32, mf->height, MIN_IN_HEIGHT, MAX_IN_HEIGHT); mf->colorspace = V4L2_COLORSPACE_JPEG; return isp_fmt; } static int sunxi_isp_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) { struct v4l2_captureparm *cp = &parms->parm.capture; struct isp_dev *isp = v4l2_get_subdevdata(sd); if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; isp->capture_mode = cp->capturemode; return 0; } static int sunxi_isp_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) { struct v4l2_captureparm *cp = &parms->parm.capture; struct isp_dev *isp = v4l2_get_subdevdata(sd); if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; memset(cp, 0, sizeof(struct v4l2_captureparm)); cp->capability = V4L2_CAP_TIMEPERFRAME; cp->capturemode = isp->capture_mode; return 0; } static int sunxi_isp_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_mbus_code_enum *code) { struct isp_pix_fmt *fmt; fmt = __isp_find_format(NULL, NULL, code->index); if (!fmt) return -EINVAL; code->code = fmt->mbus_code; return 0; } static int sunxi_isp_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_frame_size_enum *fse) { if (fse->index != 0) return -EINVAL; fse->min_width = MIN_IN_WIDTH; fse->min_height = MIN_IN_HEIGHT; fse->max_width = MAX_IN_WIDTH; fse->max_height = MAX_IN_HEIGHT; return 0; } static int sunxi_isp_subdev_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_format *fmt) { struct isp_dev *isp = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *mf; mf = __isp_get_format(isp, fh, fmt->which); if (!mf) return -EINVAL; mutex_lock(&isp->subdev_lock); fmt->format = *mf; mutex_unlock(&isp->subdev_lock); return 0; } static int sunxi_isp_subdev_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_format *fmt) { struct isp_dev *isp = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *mf; struct isp_pix_fmt *isp_fmt; vin_log(VIN_LOG_FMT, "%s %d*%d %x %d\n", __func__, fmt->format.width, fmt->format.height, fmt->format.code, fmt->format.field); mf = __isp_get_format(isp, fh, fmt->which); if (fmt->pad == ISP_PAD_SOURCE) { if (mf) { mutex_lock(&isp->subdev_lock); fmt->format = *mf; mutex_unlock(&isp->subdev_lock); } return 0; } isp_fmt = __isp_try_format(&fmt->format); if (mf) { mutex_lock(&isp->subdev_lock); *mf = fmt->format; if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) isp->isp_fmt = isp_fmt; mutex_unlock(&isp->subdev_lock); } return 0; } int sunxi_isp_subdev_init(struct v4l2_subdev *sd, u32 val) { int ret = 0; struct isp_dev *isp = v4l2_get_subdevdata(sd); if (!isp->use_isp) return 0; vin_print("%s, val = %d.\n", __func__, val); if (val) { bsp_isp_set_dma_load_addr(isp->id, (unsigned long)isp->isp_load.dma_addr); bsp_isp_set_dma_saved_addr(isp->id, (unsigned long)isp->isp_save.dma_addr); ret = isp_table_request(sd); if (ret) { vin_err("isp_table_request error at %s\n", __func__); return ret; } INIT_WORK(&isp->isp_isr_bh_task, isp_isr_bh_handle); isp_table_setting(sd); bsp_isp_clr_para_ready(isp->id); } else { flush_work(&isp->isp_isr_bh_task); isp_table_release(sd); } return 0; } static int __isp_set_load_reg(struct v4l2_subdev *sd, struct isp_table_reg_map *reg) { struct isp_dev *isp = v4l2_get_subdevdata(sd); if (!isp->use_isp) return 0; isp->load_flag = 1; return copy_from_user(isp->load_shadow, reg->addr, reg->size); } static int __isp_set_table1_map(struct v4l2_subdev *sd, struct isp_table_reg_map *tbl) { struct isp_dev *isp = v4l2_get_subdevdata(sd); int ret; if (!isp->use_isp) return 0; ret = copy_from_user(isp->isp_lut_tbl.vir_addr, tbl->addr, tbl->size); if (ret < 0) { vin_err("copy table mapping1 from usr error!\n"); return ret; } return 0; } static int __isp_set_table2_map(struct v4l2_subdev *sd, struct isp_table_reg_map *tbl) { struct isp_dev *isp = v4l2_get_subdevdata(sd); int ret; if (!isp->use_isp) return 0; ret = copy_from_user(isp->isp_drc_tbl.vir_addr, tbl->addr, tbl->size); if (ret < 0) { vin_err("copy table mapping2 from usr error!\n"); return ret; } return 0; } static long sunxi_isp_subdev_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { int ret = 0; switch (cmd) { case VIDIOC_VIN_ISP_LOAD_REG: ret = __isp_set_load_reg(sd, (struct isp_table_reg_map *)arg); break; case VIDIOC_VIN_ISP_TABLE1_MAP: ret = __isp_set_table1_map(sd, (struct isp_table_reg_map *)arg); break; case VIDIOC_VIN_ISP_TABLE2_MAP: ret = __isp_set_table2_map(sd, (struct isp_table_reg_map *)arg); break; default: return -ENOIOCTLCMD; } return ret; } #ifdef CONFIG_COMPAT struct isp_table_reg_map32 { compat_caddr_t addr; unsigned int size; }; #define VIDIOC_VIN_ISP_LOAD_REG32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 70, struct isp_table_reg_map32) #define VIDIOC_VIN_ISP_TABLE1_MAP32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 71, struct isp_table_reg_map32) #define VIDIOC_VIN_ISP_TABLE2_MAP32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 72, struct isp_table_reg_map32) static int get_isp_table_reg_map32(struct isp_table_reg_map *kp, struct isp_table_reg_map32 __user *up) { u32 tmp; if (!access_ok(VERIFY_READ, up, sizeof(struct isp_table_reg_map32)) || get_user(kp->size, &up->size) || get_user(tmp, &up->addr)) return -EFAULT; kp->addr = compat_ptr(tmp); return 0; } static int put_isp_table_reg_map32(struct isp_table_reg_map *kp, struct isp_table_reg_map32 __user *up) { u32 tmp = (u32) ((unsigned long)kp->addr); if (!access_ok(VERIFY_WRITE, up, sizeof(struct isp_table_reg_map32)) || put_user(kp->size, &up->size) || put_user(tmp, &up->addr)) return -EFAULT; return 0; } static long isp_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd, unsigned long arg) { union { struct isp_table_reg_map isd; } karg; void __user *up = compat_ptr(arg); int compatible_arg = 1; long err = 0; vin_log(VIN_LOG_ISP, "%s cmd is %d\n", __func__, cmd); switch (cmd) { case VIDIOC_VIN_ISP_LOAD_REG32: cmd = VIDIOC_VIN_ISP_LOAD_REG; break; case VIDIOC_VIN_ISP_TABLE1_MAP32: cmd = VIDIOC_VIN_ISP_TABLE1_MAP; break; case VIDIOC_VIN_ISP_TABLE2_MAP32: cmd = VIDIOC_VIN_ISP_TABLE2_MAP; break; } switch (cmd) { case VIDIOC_VIN_ISP_LOAD_REG: case VIDIOC_VIN_ISP_TABLE1_MAP: case VIDIOC_VIN_ISP_TABLE2_MAP: err = get_isp_table_reg_map32(&karg.isd, up); compatible_arg = 0; break; } if (err) return err; if (compatible_arg) err = sunxi_isp_subdev_ioctl(sd, cmd, up); else { mm_segment_t old_fs = get_fs(); set_fs(KERNEL_DS); err = sunxi_isp_subdev_ioctl(sd, cmd, &karg); set_fs(old_fs); } switch (cmd) { case VIDIOC_VIN_ISP_LOAD_REG: case VIDIOC_VIN_ISP_TABLE1_MAP: case VIDIOC_VIN_ISP_TABLE2_MAP: err = put_isp_table_reg_map32(&karg.isd, up); break; } return err; } #endif static void sunxi_isp_stat_parse(struct isp_dev *isp) { void *va = NULL; if (NULL == isp->h3a_stat.active_buf) { vin_log(VIN_LOG_ISP, "stat active buf is NULL, please enable\n"); return; } va = isp->h3a_stat.active_buf->virt_addr; isp->stat_buf->hist_buf = (void *) (va); isp->stat_buf->ae_buf = (void *) (va + ISP_STAT_AE_MEM_OFS); isp->stat_buf->af_buf = (void *) (va + ISP_STAT_AF_MEM_OFS); isp->stat_buf->afs_buf = (void *) (va + ISP_STAT_AFS_MEM_OFS); isp->stat_buf->awb_buf = (void *) (va + ISP_STAT_AWB_MEM_OFS); isp->stat_buf->pltm_buf = (void *) (va + ISP_STAT_PLTM_LST_MEM_OFS); } void sunxi_isp_vsync_isr(struct v4l2_subdev *sd) { struct isp_dev *isp = v4l2_get_subdevdata(sd); struct v4l2_event event; memset(&event, 0, sizeof(event)); event.type = V4L2_EVENT_VSYNC; event.id = 0; v4l2_event_queue(isp->subdev.devnode, &event); } void sunxi_isp_frame_sync_isr(struct v4l2_subdev *sd) { struct isp_dev *isp = v4l2_get_subdevdata(sd); struct v4l2_event event; vin_isp_stat_isr_frame_sync(&isp->h3a_stat); memset(&event, 0, sizeof(event)); event.type = V4L2_EVENT_FRAME_SYNC; event.id = 0; v4l2_event_queue(isp->subdev.devnode, &event); isp->h3a_stat.frame_number++; vin_isp_stat_isr(&isp->h3a_stat); /* you should enable the isp stat buf first, ** when you want to get the stat buf separate. ** user can get stat buf from external ioctl. */ sunxi_isp_stat_parse(isp); } int sunxi_isp_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { vin_log(VIN_LOG_ISP, "%s id = %d\n", __func__, sub->id); if (sub->type == V4L2_EVENT_CTRL) return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); else return v4l2_event_subscribe(fh, sub, 1, NULL); } int sunxi_isp_unsubscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { vin_log(VIN_LOG_ISP, "%s id = %d\n", __func__, sub->id); return v4l2_event_unsubscribe(fh, sub); } static const struct v4l2_subdev_core_ops sunxi_isp_subdev_core_ops = { .s_power = sunxi_isp_subdev_s_power, .init = sunxi_isp_subdev_init, .ioctl = sunxi_isp_subdev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = isp_compat_ioctl32, #endif .subscribe_event = sunxi_isp_subscribe_event, .unsubscribe_event = sunxi_isp_unsubscribe_event, }; static const struct v4l2_subdev_video_ops sunxi_isp_subdev_video_ops = { .s_parm = sunxi_isp_s_parm, .g_parm = sunxi_isp_g_parm, .s_crop = sunxi_isp_subdev_set_crop, .s_stream = sunxi_isp_subdev_s_stream, }; static const struct v4l2_subdev_pad_ops sunxi_isp_subdev_pad_ops = { .enum_mbus_code = sunxi_isp_enum_mbus_code, .enum_frame_size = sunxi_isp_enum_frame_size, .get_fmt = sunxi_isp_subdev_get_fmt, .set_fmt = sunxi_isp_subdev_set_fmt, }; static struct v4l2_subdev_ops sunxi_isp_subdev_ops = { .core = &sunxi_isp_subdev_core_ops, .video = &sunxi_isp_subdev_video_ops, .pad = &sunxi_isp_subdev_pad_ops, }; static int __sunxi_isp_ctrl(struct isp_dev *isp, struct v4l2_ctrl *ctrl) { int ret = 0; if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) return 0; switch (ctrl->id) { case V4L2_CID_HFLIP: break; case V4L2_CID_VFLIP: break; case V4L2_CID_ROTATE: break; default: break; } return ret; } #define ctrl_to_sunxi_isp(ctrl) \ container_of(ctrl->handler, struct isp_dev, ctrls.handler) static int sunxi_isp_s_ctrl(struct v4l2_ctrl *ctrl) { struct isp_dev *isp = ctrl_to_sunxi_isp(ctrl); unsigned long flags; int ret; vin_log(VIN_LOG_ISP, "id = %d, val = %d, cur.val = %d\n", ctrl->id, ctrl->val, ctrl->cur.val); spin_lock_irqsave(&isp->slock, flags); ret = __sunxi_isp_ctrl(isp, ctrl); spin_unlock_irqrestore(&isp->slock, flags); return ret; } static const struct v4l2_ctrl_ops sunxi_isp_ctrl_ops = { .s_ctrl = sunxi_isp_s_ctrl, }; static const struct v4l2_ctrl_config ae_win_ctrls[] = { { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AE_WIN_X1, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AE_WIN_Y1, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AE_WIN_X2, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AE_WIN_Y2, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, } }; static const struct v4l2_ctrl_config af_win_ctrls[] = { { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AF_WIN_X1, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AF_WIN_Y1, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AF_WIN_X2, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_AF_WIN_Y2, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 3264, .step = 16, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, } }; static const struct v4l2_ctrl_config wb_gain_ctrls[] = { { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_R_GAIN, .name = "R GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 1024, .step = 1, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_GR_GAIN, .name = "GR GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 1024, .step = 1, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_GB_GAIN, .name = "GB GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 1024, .step = 1, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, }, { .ops = &sunxi_isp_ctrl_ops, .id = V4L2_CID_B_GAIN, .name = "B GAIN", .type = V4L2_CTRL_TYPE_INTEGER, .min = 32, .max = 1024, .step = 1, .def = 256, .flags = V4L2_CTRL_FLAG_VOLATILE, } }; int __isp_init_subdev(struct isp_dev *isp) { const struct v4l2_ctrl_ops *ops = &sunxi_isp_ctrl_ops; struct v4l2_ctrl_handler *handler = &isp->ctrls.handler; struct v4l2_subdev *sd = &isp->subdev; struct sunxi_isp_ctrls *ctrls = &isp->ctrls; int i, ret; mutex_init(&isp->subdev_lock); v4l2_subdev_init(sd, &sunxi_isp_subdev_ops); sd->grp_id = VIN_GRP_ID_ISP; sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; snprintf(sd->name, sizeof(sd->name), "sunxi_isp.%u", isp->id); v4l2_set_subdevdata(sd, isp); v4l2_ctrl_handler_init(handler, 3 + ARRAY_SIZE(ae_win_ctrls) + ARRAY_SIZE(af_win_ctrls) + ARRAY_SIZE(wb_gain_ctrls)); ctrls->rotate = v4l2_ctrl_new_std(handler, ops, V4L2_CID_ROTATE, 0, 270, 90, 0); ctrls->hflip = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); ctrls->vflip = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); for (i = 0; i < ARRAY_SIZE(wb_gain_ctrls); i++) ctrls->wb_gain[i] = v4l2_ctrl_new_custom(handler, &wb_gain_ctrls[i], NULL); v4l2_ctrl_cluster(ARRAY_SIZE(wb_gain_ctrls), &ctrls->wb_gain[0]); for (i = 0; i < ARRAY_SIZE(ae_win_ctrls); i++) ctrls->ae_win[i] = v4l2_ctrl_new_custom(handler, &ae_win_ctrls[i], NULL); v4l2_ctrl_cluster(ARRAY_SIZE(ae_win_ctrls), &ctrls->ae_win[0]); for (i = 0; i < ARRAY_SIZE(af_win_ctrls); i++) ctrls->af_win[i] = v4l2_ctrl_new_custom(handler, &af_win_ctrls[i], NULL); v4l2_ctrl_cluster(ARRAY_SIZE(af_win_ctrls), &ctrls->af_win[0]); if (handler->error) { return handler->error; } /*sd->entity->ops = &isp_media_ops;*/ isp->isp_pads[ISP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; isp->isp_pads[ISP_PAD_SOURCE_ST].flags = MEDIA_PAD_FL_SOURCE; isp->isp_pads[ISP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV; ret = media_entity_init(&sd->entity, ISP_PAD_NUM, isp->isp_pads, 0); if (ret < 0) return ret; sd->ctrl_handler = handler; /*sd->internal_ops = &sunxi_isp_sd_internal_ops;*/ return 0; } void isp_table_setting(struct v4l2_subdev *sd) { struct isp_dev *isp = v4l2_get_subdevdata(sd); printk("isp->use_cnt = %d\n", isp->use_cnt); if (isp->use_cnt > 1) return; if (isp->is_raw && isp->use_isp) { bsp_isp_update_lens_gamma_table(isp->id, &isp->isp_tbl); bsp_isp_update_drc_table(isp->id, &isp->isp_tbl); } } int isp_table_request(struct v4l2_subdev *sd) { struct isp_dev *isp = v4l2_get_subdevdata(sd); struct isp_table_addr *tbl = &isp->isp_tbl; void *pa, *va, *dma; int ret; if (isp->use_cnt > 1) return 0; /*requeset for isp table and statistic buffer*/ if (isp->use_isp && isp->is_raw) { isp->isp_lut_tbl.size = ISP_TABLE_MAPPING1_SIZE; ret = os_mem_alloc(&isp->pdev->dev, &isp->isp_lut_tbl); if (ret < 0) { vin_err("isp lookup table request failed!\n"); return -ENOMEM; } pa = isp->isp_lut_tbl.phy_addr; va = isp->isp_lut_tbl.vir_addr; dma = isp->isp_lut_tbl.dma_addr; tbl->isp_lsc_tbl_paddr = (void *)(pa + ISP_LSC_MEM_OFS); tbl->isp_lsc_tbl_dma_addr = (void *)(dma + ISP_LSC_MEM_OFS); tbl->isp_lsc_tbl_vaddr = (void *)(va + ISP_LSC_MEM_OFS); tbl->isp_gamma_tbl_paddr = (void *)(pa + ISP_GAMMA_MEM_OFS); tbl->isp_gamma_tbl_dma_addr = (void *)(dma + ISP_GAMMA_MEM_OFS); tbl->isp_gamma_tbl_vaddr = (void *)(va + ISP_GAMMA_MEM_OFS); tbl->isp_linear_tbl_paddr = (void *)(pa + ISP_LINEAR_MEM_OFS); tbl->isp_linear_tbl_dma_addr = (void *)(dma + ISP_LINEAR_MEM_OFS); tbl->isp_linear_tbl_vaddr = (void *)(va + ISP_LINEAR_MEM_OFS); vin_log(VIN_LOG_ISP, "isp_lsc_tbl_vaddr = %p\n", tbl->isp_lsc_tbl_vaddr); vin_log(VIN_LOG_ISP, "isp_gamma_tbl_vaddr = %p\n", tbl->isp_gamma_tbl_vaddr); isp->isp_drc_tbl.size = ISP_TABLE_MAPPING2_SIZE; ret = os_mem_alloc(&isp->pdev->dev, &isp->isp_drc_tbl); if (ret < 0) { vin_err("isp drc table request pa failed!\n"); return -ENOMEM; } pa = isp->isp_drc_tbl.phy_addr; va = isp->isp_drc_tbl.vir_addr; dma = isp->isp_drc_tbl.dma_addr; tbl->isp_drc_tbl_paddr = (void *)(pa + ISP_DRC_MEM_OFS); tbl->isp_drc_tbl_dma_addr = (void *)(dma + ISP_DRC_MEM_OFS); tbl->isp_drc_tbl_vaddr = (void *)(va + ISP_DRC_MEM_OFS); vin_log(VIN_LOG_ISP, "isp_drc_tbl_vaddr = %p\n", tbl->isp_drc_tbl_vaddr); } return 0; } /*static void isp_table_release(struct isp_dev *isp)*/ void isp_table_release(struct v4l2_subdev *sd) { struct isp_dev *isp = v4l2_get_subdevdata(sd); if (isp->use_cnt == 0) return; /* release isp table and statistic buffer */ if (isp->use_isp && isp->is_raw) { os_mem_free(&isp->pdev->dev, &isp->isp_lut_tbl); os_mem_free(&isp->pdev->dev, &isp->isp_drc_tbl); } } static int isp_resource_alloc(struct isp_dev *isp) { int ret = 0; isp->isp_load.size = ISP_LOAD_REG_SIZE; isp->isp_save.size = ISP_SAVED_REG_SIZE; ret = os_mem_alloc(&isp->pdev->dev, &isp->isp_load); if (ret < 0) { vin_err("isp load regs requset add failed!\n"); return -ENOMEM; } ret = os_mem_alloc(&isp->pdev->dev, &isp->isp_save); if (ret < 0) { vin_err("isp save regs requset add failed!\n"); return -ENOMEM; } return ret; } static void isp_resource_free(struct isp_dev *isp) { os_mem_free(&isp->pdev->dev, &isp->isp_load); os_mem_free(&isp->pdev->dev, &isp->isp_save); } #define ISP_REGS(n) (void __iomem *)(ISP_REGS_BASE + n) void isp_isr_bh_handle(struct work_struct *work) { struct isp_dev *isp = container_of(work, struct isp_dev, isp_isr_bh_task); if (isp->is_raw) { mutex_lock(&isp->subdev_lock); if (1 == isp_reparse_flag) { vin_print("ISP reparse ini file!\n"); isp_reparse_flag = 0; } else if (2 == isp_reparse_flag) { vin_reg_set(ISP_REGS(0x10), (1 << 20)); } else if (3 == isp_reparse_flag) { vin_reg_clr_set(ISP_REGS(0x10), (0xF << 16), (1 << 16)); vin_reg_set(ISP_REGS(0x10), (1 << 20)); } else if (4 == isp_reparse_flag) { vin_reg_clr(ISP_REGS(0x10), (1 << 20)); vin_reg_clr(ISP_REGS(0x10), (0xF << 16)); } mutex_unlock(&isp->subdev_lock); } } static irqreturn_t isp_isr(int irq, void *priv) { struct isp_dev *isp = (struct isp_dev *)priv; unsigned int update_flag; unsigned long flags; if (!isp->use_isp) return 0; vin_log(VIN_LOG_ISP, "isp %d interrupt, status is 0x%x!!!\n", isp->id, bsp_isp_get_irq_status(isp->id, ISP_IRQ_EN_ALL)); spin_lock_irqsave(&isp->slock, flags); if (bsp_isp_get_irq_status(isp->id, SRC0_FIFO_INT_EN)) { vin_err("isp source0 fifo overflow\n"); bsp_isp_clr_irq_status(isp->id, SRC0_FIFO_INT_EN); goto unlock; } if (bsp_isp_get_irq_status(isp->id, FRAME_ERROR_INT_EN)) { vin_err("isp frame error\n"); bsp_isp_clr_irq_status(isp->id, FRAME_ERROR_INT_EN); goto unlock; } if (bsp_isp_get_irq_status(isp->id, FRAME_LOST_INT_EN)) { vin_err("isp frame lost\n"); bsp_isp_clr_irq_status(isp->id, FRAME_LOST_INT_EN); goto unlock; } unlock: spin_unlock_irqrestore(&isp->slock, flags); if (bsp_isp_get_irq_status(isp->id, FINISH_INT_EN)) { bsp_isp_irq_disable(isp->id, FINISH_INT_EN); vin_log(VIN_LOG_ISP, "call tasklet schedule!\n"); bsp_isp_clr_para_ready(isp->id); schedule_work(&isp->isp_isr_bh_task); if (isp->load_flag) { memcpy(isp->isp_load.vir_addr, isp->load_shadow, 0x400); isp->load_flag = 0; } bsp_isp_set_ob_zone(isp->id, &isp->isp_ob); update_flag = readl(isp->isp_load.vir_addr + 0x04); bsp_isp_update_table(isp->id, (unsigned short)update_flag); isp_3d_pingpong_update(isp, &isp->isp_3d_buf); bsp_isp_set_para_ready(isp->id); sunxi_isp_frame_sync_isr(&isp->subdev); } bsp_isp_clr_irq_status(isp->id, FINISH_INT_EN); bsp_isp_irq_enable(isp->id, FINISH_INT_EN); return IRQ_HANDLED; } unsigned int isp_default_reg[0x100] = { 0x00000101, 0x00000001, 0x00004111, 0x00000087, 0x03c00010, 0x00000000, 0x28000000, 0x04000000, 0x0dc11000, 0x0dc11400, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000004, 0x00000000, 0x0136003c, 0x00000106, 0x00005040, 0x00000000, 0x00000000, 0x00000000, 0x000f0013, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01e00280, 0x01e00280, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0f000200, 0x01390010, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000804, 0x00000000, 0x00000000, 0x00000000, 0x00021010, 0x00000000, 0x00000000, 0x00000000, 0x00400010, 0x01000100, 0x00200020, 0x00000100, 0x00200020, 0x00200020, 0x04000400, 0x04000400, 0x00200020, 0x00200020, 0x00ff00ff, 0x000000ff, 0x000f0013, 0x00000000, 0x00000000, 0x00000000, 0x00080008, 0x00000000, 0x00000000, 0x00000000, 0x40070f01, 0xfcff0080, 0x1f173c2d, 0x001845c8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01000100, 0x01000100, 0x00000fff, 0x00000010, 0x30000000, 0x00000080, 0x0003875c, 0x00400010, 0x02000200, 0x04000400, 0x00000000, 0x00000484, 0x00000808, 0x00420077, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x02040107, 0x07680064, 0x01c206d6, 0x068701c2, 0x000007b7, 0x02010010, 0x00000008, 0x00000000, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00000000, 0x00000000, 0x00000000, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x02108421, 0x02108421, 0x02108421, 0x02108421, 0x02108421, 0x02108421, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00000000, 0x00000000, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00080008, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }; static int isp_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct isp_dev *isp = NULL; int ret = 0; if (np == NULL) { vin_err("ISP failed to get of node\n"); return -ENODEV; } isp = kzalloc(sizeof(struct isp_dev), GFP_KERNEL); if (!isp) { ret = -ENOMEM; goto ekzalloc; } isp->stat_buf = kzalloc(sizeof(struct isp_stat_to_user), GFP_KERNEL); if (!isp->stat_buf) { vin_err("request stat_buf struct failed!\n"); return -ENOMEM; } of_property_read_u32(np, "device_id", &pdev->id); if (pdev->id < 0) { vin_err("ISP failed to get device id\n"); ret = -EINVAL; goto freedev; } isp->id = pdev->id; isp->pdev = pdev; isp->base = of_iomap(np, 0); if (!isp->base) { isp->is_empty = 1; isp->base = kzalloc(0x300, GFP_KERNEL); if (!isp->base) { ret = -EIO; goto freedev; } } else { isp->is_empty = 0; /*get irq resource */ isp->irq = irq_of_parse_and_map(np, 0); if (isp->irq <= 0) { vin_err("failed to get ISP IRQ resource\n"); goto unmap; } ret = request_irq(isp->irq, isp_isr, IRQF_DISABLED, isp->pdev->name, isp); if (ret) { vin_err("isp%d request irq failed\n", isp->id); goto unmap; } } list_add_tail(&isp->isp_list, &isp_drv_list); __isp_init_subdev(isp); spin_lock_init(&isp->slock); if (isp_resource_alloc(isp) < 0) { ret = -ENOMEM; goto freeirq; } ret = vin_isp_h3a_init(isp); if (ret < 0) { vin_err("VIN H3A initialization failed\n"); goto free_res; } bsp_isp_init_platform(SUNXI_PLATFORM_ID); bsp_isp_set_base_addr(isp->id, (unsigned long)isp->base); bsp_isp_set_map_load_addr(isp->id, (unsigned long)isp->isp_load.vir_addr); bsp_isp_set_map_saved_addr(isp->id, (unsigned long)isp->isp_save.vir_addr); memset(isp->isp_load.vir_addr, 0, ISP_LOAD_REG_SIZE); memset(isp->isp_save.vir_addr, 0, ISP_SAVED_REG_SIZE); memcpy(isp->isp_load.vir_addr, &isp_default_reg[0], 0x400); platform_set_drvdata(pdev, isp); vin_print("isp%d probe end!\n", isp->id); return 0; free_res: isp_resource_free(isp); freeirq: if (!isp->is_empty) free_irq(isp->irq, isp); unmap: if (!isp->is_empty) iounmap(isp->base); else kfree(isp->base); list_del(&isp->isp_list); freedev: kfree(isp); ekzalloc: vin_print("isp probe err!\n"); return ret; } static int isp_remove(struct platform_device *pdev) { struct isp_dev *isp = platform_get_drvdata(pdev); struct v4l2_subdev *sd = &isp->subdev; platform_set_drvdata(pdev, NULL); v4l2_ctrl_handler_free(sd->ctrl_handler); v4l2_set_subdevdata(sd, NULL); isp_resource_free(isp); if (!isp->is_empty) { free_irq(isp->irq, isp); if (isp->base) iounmap(isp->base); } else { kfree(isp->base); } list_del(&isp->isp_list); kfree(isp->stat_buf); vin_isp_h3a_cleanup(isp); kfree(isp); return 0; } static const struct of_device_id sunxi_isp_match[] = { {.compatible = "allwinner,sunxi-isp",}, {}, }; static struct platform_driver isp_platform_driver = { .probe = isp_probe, .remove = isp_remove, .driver = { .name = ISP_MODULE_NAME, .owner = THIS_MODULE, .of_match_table = sunxi_isp_match, } }; void sunxi_isp_sensor_type(struct v4l2_subdev *sd, int use_isp, int is_raw) { struct isp_dev *isp = v4l2_get_subdevdata(sd); isp->use_isp = use_isp; isp->is_raw = is_raw; } void sunxi_isp_dump_regs(struct v4l2_subdev *sd) { struct isp_dev *isp = v4l2_get_subdevdata(sd); int i = 0; printk("vin dump ISP regs :\n"); for (i = 0; i < 0x40; i = i + 4) { if (i % 0x10 == 0) printk("0x%08x: ", i); printk("0x%08x, ", readl(isp->base + i)); if (i % 0x10 == 0xc) printk("\n"); } for (i = 0x40; i < 0x240; i = i + 4) { if (i % 0x10 == 0) printk("0x%08x: ", i); printk("0x%08x, ", readl(isp->isp_load.vir_addr + i)); if (i % 0x10 == 0xc) printk("\n"); } } struct v4l2_subdev *sunxi_isp_get_subdev(int id) { struct isp_dev *isp; list_for_each_entry(isp, &isp_drv_list, isp_list) { if (isp->id == id) { isp->use_cnt++; return &isp->subdev; } } return NULL; } struct v4l2_subdev *sunxi_stat_get_subdev(int id) { struct isp_dev *isp; list_for_each_entry(isp, &isp_drv_list, isp_list) { if (isp->id == id) { return &isp->h3a_stat.sd; } } return NULL; } int sunxi_isp_platform_register(void) { return platform_driver_register(&isp_platform_driver); } void sunxi_isp_platform_unregister(void) { platform_driver_unregister(&isp_platform_driver); vin_print("isp_exit end\n"); }