/* * linux-3.10/drivers/media/platform/sunxi-vin/vin-stat/vin_h3a.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. * */ /* * vin_3a.c * */ #include #include #include "vin_h3a.h" #include "vin_ispstat.h" /* * h3a_setup_regs - Helper function to update h3a registers. */ static void h3a_setup_regs(struct isp_stat *h3a, void *priv) { struct vin_isp_h3a_config *conf = priv; struct isp_dev *isp = container_of(h3a, struct isp_dev, h3a_stat); dma_addr_t dma_addr = (dma_addr_t)(h3a->active_buf->dma_addr); if (h3a->state == ISPSTAT_DISABLED) return; bsp_isp_set_statistics_addr(isp->id, dma_addr); if (!h3a->update) return; h3a->update = 0; h3a->config_counter += h3a->inc_config; h3a->inc_config = 0; h3a->buf_size = conf->buf_size; } static void h3a_enable(struct isp_stat *h3a, int enable) { } static int h3a_busy(struct isp_stat *h3a) { return 0; } static u32 h3a_get_buf_size(struct vin_isp_h3a_config *conf) { return ISP_STAT_TOTAL_SIZE; } static int h3a_validate_params(struct isp_stat *aewb, void *new_conf) { struct vin_isp_h3a_config *user_cfg = new_conf; u32 buf_size; buf_size = h3a_get_buf_size(user_cfg); if (buf_size > user_cfg->buf_size) user_cfg->buf_size = buf_size; else if (user_cfg->buf_size > ISP_STAT_TOTAL_SIZE) user_cfg->buf_size = ISP_STAT_TOTAL_SIZE; return 0; } static void h3a_set_params(struct isp_stat *stat, void *new_conf) { /*struct vin_isp_h3a_config *user_cfg = new_conf;*/ struct vin_isp_h3a_config *cur_cfg = stat->priv; int update = 0; /* * check the privat field, which will write to the hardware later. */ if (update || !stat->configured) { stat->inc_config++; stat->update = 1; cur_cfg->buf_size = h3a_get_buf_size(cur_cfg); } } int isp_ae_stat_req(struct isp_dev *isp, struct isp_stat_buf *ae_buf) { int ret = 0; if (isp->stat_buf->ae_buf == NULL) return -EINVAL; ae_buf->buf_size = ISP_STAT_AE_MEM_SIZE; ret = copy_to_user(ae_buf->buf, isp->stat_buf->ae_buf, ae_buf->buf_size); return ret; } int isp_gamma_req(struct isp_dev *isp, struct isp_stat_buf *gamma_buf) { int ret = 0; if (isp->isp_tbl.isp_gamma_tbl_vaddr == NULL) return -EINVAL; gamma_buf->buf_size = ISP_GAMMA_MEM_SIZE; ret = copy_to_user(gamma_buf->buf, isp->isp_tbl.isp_gamma_tbl_vaddr, gamma_buf->buf_size); return ret; } int isp_hist_stat_req(struct isp_dev *isp, struct isp_stat_buf *hist_buf) { int ret = 0; if (isp->stat_buf->hist_buf == NULL) return -EINVAL; hist_buf->buf_size = ISP_STAT_HIST_MEM_SIZE; ret = copy_to_user(hist_buf->buf, isp->stat_buf->hist_buf, hist_buf->buf_size); return ret; } int isp_af_stat_req(struct isp_dev *isp, struct isp_stat_buf *af_buf) { int ret = 0; if (isp->stat_buf->af_buf == NULL) return -EINVAL; af_buf->buf_size = ISP_STAT_AF_MEM_SIZE; ret = copy_to_user(af_buf->buf, isp->stat_buf->af_buf, af_buf->buf_size); return ret; } static long h3a_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { struct isp_stat *stat = v4l2_get_subdevdata(sd); vin_log(VIN_LOG_STAT, "%s cmd is 0x%x\n", __func__, cmd); switch (cmd) { case VIDIOC_ISP_AE_STAT_REQ: return isp_ae_stat_req(stat->isp, arg); case VIDIOC_ISP_AF_STAT_REQ: return isp_af_stat_req(stat->isp, arg); case VIDIOC_ISP_HIST_STAT_REQ: return isp_hist_stat_req(stat->isp, arg); case VIDIOC_ISP_GAMMA_REQ: return isp_gamma_req(stat->isp, arg); case VIDIOC_VIN_ISP_H3A_CFG: return vin_isp_stat_config(stat, arg); case VIDIOC_VIN_ISP_STAT_REQ: return vin_isp_stat_request_statistics(stat, arg); case VIDIOC_VIN_ISP_STAT_EN: { unsigned int *en = arg; return vin_isp_stat_enable(stat, !!*en); } } return -ENOIOCTLCMD; } #ifdef CONFIG_COMPAT struct isp_stat_buf32 { compat_caddr_t buf; __u32 buf_size; }; static int get_isp_stat_buf32(struct isp_stat_buf *kp, struct isp_stat_buf32 __user *up) { u32 tmp; if (!access_ok(VERIFY_READ, up, sizeof(struct isp_stat_buf32)) || get_user(kp->buf_size, &up->buf_size) || get_user(tmp, &up->buf)) return -EFAULT; kp->buf = compat_ptr(tmp); return 0; } static int put_isp_stat_buf32(struct isp_stat_buf *kp, struct isp_stat_buf32 __user *up) { u32 tmp = (u32) ((unsigned long)kp->buf); if (!access_ok(VERIFY_WRITE, up, sizeof(struct isp_stat_buf32)) || put_user(kp->buf_size, &up->buf_size) || put_user(tmp, &up->buf)) return -EFAULT; return 0; } struct vin_isp_stat_data32 { compat_caddr_t buf; __u32 buf_size; __u32 frame_number; __u32 config_counter; }; static int get_isp_statistics_buf32(struct vin_isp_stat_data *kp, struct vin_isp_stat_data32 __user *up) { u32 tmp; if (!access_ok(VERIFY_READ, up, sizeof(struct vin_isp_stat_data32)) || get_user(kp->buf_size, &up->buf_size) || get_user(kp->frame_number, &up->frame_number) || get_user(kp->config_counter, &up->config_counter) || get_user(tmp, &up->buf)) return -EFAULT; kp->buf = compat_ptr(tmp); return 0; } static int put_isp_statistics_buf32(struct vin_isp_stat_data *kp, struct vin_isp_stat_data32 __user *up) { u32 tmp = (u32) ((unsigned long)kp->buf); if (!access_ok(VERIFY_READ, up, sizeof(struct vin_isp_stat_data32)) || put_user(kp->buf_size, &up->buf_size) || put_user(kp->frame_number, &up->frame_number) || put_user(kp->config_counter, &up->config_counter) || put_user(tmp, &up->buf)) return -EFAULT; return 0; } #define VIDIOC_ISP_AE_STAT_REQ32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 1, struct isp_stat_buf32) #define VIDIOC_ISP_HIST_STAT_REQ32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 2, struct isp_stat_buf32) #define VIDIOC_ISP_AF_STAT_REQ32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 3, struct isp_stat_buf32) #define VIDIOC_ISP_GAMMA_REQ32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 5, struct isp_stat_buf32) #define VIDIOC_VIN_ISP_STAT_REQ32 \ _IOWR('V', BASE_VIDIOC_PRIVATE + 32, struct vin_isp_stat_data32) static long h3a_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd, unsigned long arg) { union { struct isp_stat_buf isb; struct vin_isp_stat_data isd; } karg; void __user *up = compat_ptr(arg); int compatible_arg = 1; long err = 0; vin_log(VIN_LOG_STAT, "%s cmd is 0x%x\n", __func__, cmd); switch (cmd) { case VIDIOC_ISP_AE_STAT_REQ32: cmd = VIDIOC_ISP_AE_STAT_REQ; break; case VIDIOC_ISP_HIST_STAT_REQ32: cmd = VIDIOC_ISP_HIST_STAT_REQ; break; case VIDIOC_ISP_AF_STAT_REQ32: cmd = VIDIOC_ISP_AF_STAT_REQ; break; case VIDIOC_ISP_GAMMA_REQ32: cmd = VIDIOC_ISP_GAMMA_REQ; break; case VIDIOC_VIN_ISP_STAT_REQ32: cmd = VIDIOC_VIN_ISP_STAT_REQ; break; } switch (cmd) { case VIDIOC_ISP_AE_STAT_REQ: case VIDIOC_ISP_HIST_STAT_REQ: case VIDIOC_ISP_AF_STAT_REQ: case VIDIOC_ISP_GAMMA_REQ: err = get_isp_stat_buf32(&karg.isb, up); compatible_arg = 0; break; case VIDIOC_VIN_ISP_STAT_REQ: err = get_isp_statistics_buf32(&karg.isd, up); compatible_arg = 0; break; } if (err) return err; if (compatible_arg) err = h3a_ioctl(sd, cmd, up); else { mm_segment_t old_fs = get_fs(); set_fs(KERNEL_DS); err = h3a_ioctl(sd, cmd, &karg); set_fs(old_fs); } switch (cmd) { case VIDIOC_ISP_AE_STAT_REQ: case VIDIOC_ISP_HIST_STAT_REQ: case VIDIOC_ISP_AF_STAT_REQ: case VIDIOC_ISP_GAMMA_REQ: err = put_isp_stat_buf32(&karg.isb, up); break; case VIDIOC_VIN_ISP_STAT_REQ: err = put_isp_statistics_buf32(&karg.isd, up); break; } return err; } #endif static const struct ispstat_ops h3a_ops = { .validate_params = h3a_validate_params, .set_params = h3a_set_params, .setup_regs = h3a_setup_regs, .enable = h3a_enable, .busy = h3a_busy, }; static const struct v4l2_subdev_core_ops h3a_subdev_core_ops = { .ioctl = h3a_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = h3a_compat_ioctl32, #endif .subscribe_event = vin_isp_stat_subscribe_event, .unsubscribe_event = vin_isp_stat_unsubscribe_event, }; static const struct v4l2_subdev_video_ops h3a_subdev_video_ops = { .s_stream = vin_isp_stat_s_stream, }; static const struct v4l2_subdev_ops h3a_subdev_ops = { .core = &h3a_subdev_core_ops, .video = &h3a_subdev_video_ops, }; int vin_isp_h3a_init(struct isp_dev *isp) { struct isp_stat *stat = &isp->h3a_stat; struct vin_isp_h3a_config *cfg; struct vin_isp_h3a_config *recover_cfg; char name[32]; vin_print("%s\n", __func__); cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); if (!cfg) return -ENOMEM; stat->ops = &h3a_ops; stat->priv = cfg; stat->isp = isp; stat->event_type = V4L2_EVENT_VIN_H3A; recover_cfg = kzalloc(sizeof(*recover_cfg), GFP_KERNEL); if (!recover_cfg) { vin_err("H3A: cannot allocate memory for recover config.\n"); return -ENOMEM; } if (h3a_validate_params(stat, recover_cfg)) { vin_err("H3A: recover configuration is invalid.\n"); return -EINVAL; } recover_cfg->buf_size = h3a_get_buf_size(recover_cfg); stat->recover_priv = recover_cfg; snprintf(name, sizeof(name), "h3a.%u", isp->id); return vin_isp_stat_init(stat, &name[0], &h3a_subdev_ops); } void vin_isp_h3a_cleanup(struct isp_dev *isp) { vin_isp_stat_cleanup(&isp->h3a_stat); vin_print("%s\n", __func__); }