// SPDX-License-Identifier: GPL-2.0-only 
 | 
/* 
 | 
 * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver 
 | 
 * 
 | 
 * Copyright (C) 2013 Samsung Electronics Co., Ltd. 
 | 
 * 
 | 
 * Authors: Sylwester Nawrocki <s.nawrocki@samsung.com> 
 | 
 *          Younghwan Joo <yhwan.joo@samsung.com> 
 | 
 */ 
 | 
#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ 
 | 
  
 | 
#include <linux/device.h> 
 | 
#include <linux/errno.h> 
 | 
#include <linux/kernel.h> 
 | 
#include <linux/list.h> 
 | 
#include <linux/module.h> 
 | 
#include <linux/platform_device.h> 
 | 
#include <linux/printk.h> 
 | 
#include <linux/pm_runtime.h> 
 | 
#include <linux/slab.h> 
 | 
#include <linux/types.h> 
 | 
#include <media/v4l2-device.h> 
 | 
  
 | 
#include "media-dev.h" 
 | 
#include "fimc-isp-video.h" 
 | 
#include "fimc-is-command.h" 
 | 
#include "fimc-is-param.h" 
 | 
#include "fimc-is-regs.h" 
 | 
#include "fimc-is.h" 
 | 
  
 | 
int fimc_isp_debug; 
 | 
module_param_named(debug_isp, fimc_isp_debug, int, S_IRUGO | S_IWUSR); 
 | 
  
 | 
static const struct fimc_fmt fimc_isp_formats[FIMC_ISP_NUM_FORMATS] = { 
 | 
    { 
 | 
        .fourcc        = V4L2_PIX_FMT_SGRBG8, 
 | 
        .depth        = { 8 }, 
 | 
        .color        = FIMC_FMT_RAW8, 
 | 
        .memplanes    = 1, 
 | 
        .mbus_code    = MEDIA_BUS_FMT_SGRBG8_1X8, 
 | 
    }, { 
 | 
        .fourcc        = V4L2_PIX_FMT_SGRBG10, 
 | 
        .depth        = { 10 }, 
 | 
        .color        = FIMC_FMT_RAW10, 
 | 
        .memplanes    = 1, 
 | 
        .mbus_code    = MEDIA_BUS_FMT_SGRBG10_1X10, 
 | 
    }, { 
 | 
        .fourcc        = V4L2_PIX_FMT_SGRBG12, 
 | 
        .depth        = { 12 }, 
 | 
        .color        = FIMC_FMT_RAW12, 
 | 
        .memplanes    = 1, 
 | 
        .mbus_code    = MEDIA_BUS_FMT_SGRBG12_1X12, 
 | 
    }, 
 | 
}; 
 | 
  
 | 
/** 
 | 
 * fimc_isp_find_format - lookup color format by fourcc or media bus code 
 | 
 * @pixelformat: fourcc to match, ignored if null 
 | 
 * @mbus_code: media bus code to match, ignored if null 
 | 
 * @index: index to the fimc_isp_formats array, ignored if negative 
 | 
 */ 
 | 
const struct fimc_fmt *fimc_isp_find_format(const u32 *pixelformat, 
 | 
                    const u32 *mbus_code, int index) 
 | 
{ 
 | 
    const struct fimc_fmt *fmt, *def_fmt = NULL; 
 | 
    unsigned int i; 
 | 
    int id = 0; 
 | 
  
 | 
    if (index >= (int)ARRAY_SIZE(fimc_isp_formats)) 
 | 
        return NULL; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(fimc_isp_formats); ++i) { 
 | 
        fmt = &fimc_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; 
 | 
} 
 | 
  
 | 
void fimc_isp_irq_handler(struct fimc_is *is) 
 | 
{ 
 | 
    is->i2h_cmd.args[0] = mcuctl_read(is, MCUCTL_REG_ISSR(20)); 
 | 
    is->i2h_cmd.args[1] = mcuctl_read(is, MCUCTL_REG_ISSR(21)); 
 | 
  
 | 
    fimc_is_fw_clear_irq1(is, FIMC_IS_INT_FRAME_DONE_ISP); 
 | 
    fimc_isp_video_irq_handler(is); 
 | 
  
 | 
    wake_up(&is->irq_queue); 
 | 
} 
 | 
  
 | 
/* Capture subdev media entity operations */ 
 | 
static int fimc_is_link_setup(struct media_entity *entity, 
 | 
                const struct media_pad *local, 
 | 
                const struct media_pad *remote, u32 flags) 
 | 
{ 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct media_entity_operations fimc_is_subdev_media_ops = { 
 | 
    .link_setup = fimc_is_link_setup, 
 | 
}; 
 | 
  
 | 
static int fimc_is_subdev_enum_mbus_code(struct v4l2_subdev *sd, 
 | 
                struct v4l2_subdev_pad_config *cfg, 
 | 
                struct v4l2_subdev_mbus_code_enum *code) 
 | 
{ 
 | 
    const struct fimc_fmt *fmt; 
 | 
  
 | 
    fmt = fimc_isp_find_format(NULL, NULL, code->index); 
 | 
    if (!fmt) 
 | 
        return -EINVAL; 
 | 
    code->code = fmt->mbus_code; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int fimc_isp_subdev_get_fmt(struct v4l2_subdev *sd, 
 | 
                   struct v4l2_subdev_pad_config *cfg, 
 | 
                   struct v4l2_subdev_format *fmt) 
 | 
{ 
 | 
    struct fimc_isp *isp = v4l2_get_subdevdata(sd); 
 | 
    struct v4l2_mbus_framefmt *mf = &fmt->format; 
 | 
  
 | 
    if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { 
 | 
        *mf = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); 
 | 
        return 0; 
 | 
    } 
 | 
  
 | 
    mf->colorspace = V4L2_COLORSPACE_SRGB; 
 | 
  
 | 
    mutex_lock(&isp->subdev_lock); 
 | 
  
 | 
    if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { 
 | 
        /* ISP OTF input image format */ 
 | 
        *mf = isp->sink_fmt; 
 | 
    } else { 
 | 
        /* ISP OTF output image format */ 
 | 
        *mf = isp->src_fmt; 
 | 
  
 | 
        if (fmt->pad == FIMC_ISP_SD_PAD_SRC_FIFO) { 
 | 
            mf->colorspace = V4L2_COLORSPACE_JPEG; 
 | 
            mf->code = MEDIA_BUS_FMT_YUV10_1X30; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    mutex_unlock(&isp->subdev_lock); 
 | 
  
 | 
    isp_dbg(1, sd, "%s: pad%d: fmt: 0x%x, %dx%d\n", __func__, 
 | 
        fmt->pad, mf->code, mf->width, mf->height); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void __isp_subdev_try_format(struct fimc_isp *isp, 
 | 
                    struct v4l2_subdev_pad_config *cfg, 
 | 
                    struct v4l2_subdev_format *fmt) 
 | 
{ 
 | 
    struct v4l2_mbus_framefmt *mf = &fmt->format; 
 | 
    struct v4l2_mbus_framefmt *format; 
 | 
  
 | 
    mf->colorspace = V4L2_COLORSPACE_SRGB; 
 | 
  
 | 
    if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { 
 | 
        v4l_bound_align_image(&mf->width, FIMC_ISP_SINK_WIDTH_MIN, 
 | 
                FIMC_ISP_SINK_WIDTH_MAX, 0, 
 | 
                &mf->height, FIMC_ISP_SINK_HEIGHT_MIN, 
 | 
                FIMC_ISP_SINK_HEIGHT_MAX, 0, 0); 
 | 
        mf->code = MEDIA_BUS_FMT_SGRBG10_1X10; 
 | 
    } else { 
 | 
        if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) 
 | 
            format = v4l2_subdev_get_try_format(&isp->subdev, cfg, 
 | 
                        FIMC_ISP_SD_PAD_SINK); 
 | 
        else 
 | 
            format = &isp->sink_fmt; 
 | 
  
 | 
        /* Allow changing format only on sink pad */ 
 | 
        mf->width = format->width - FIMC_ISP_CAC_MARGIN_WIDTH; 
 | 
        mf->height = format->height - FIMC_ISP_CAC_MARGIN_HEIGHT; 
 | 
  
 | 
        if (fmt->pad == FIMC_ISP_SD_PAD_SRC_FIFO) { 
 | 
            mf->code = MEDIA_BUS_FMT_YUV10_1X30; 
 | 
            mf->colorspace = V4L2_COLORSPACE_JPEG; 
 | 
        } else { 
 | 
            mf->code = format->code; 
 | 
        } 
 | 
    } 
 | 
} 
 | 
  
 | 
static int fimc_isp_subdev_set_fmt(struct v4l2_subdev *sd, 
 | 
                   struct v4l2_subdev_pad_config *cfg, 
 | 
                   struct v4l2_subdev_format *fmt) 
 | 
{ 
 | 
    struct fimc_isp *isp = v4l2_get_subdevdata(sd); 
 | 
    struct fimc_is *is = fimc_isp_to_is(isp); 
 | 
    struct v4l2_mbus_framefmt *mf = &fmt->format; 
 | 
    int ret = 0; 
 | 
  
 | 
    isp_dbg(1, sd, "%s: pad%d: code: 0x%x, %dx%d\n", 
 | 
         __func__, fmt->pad, mf->code, mf->width, mf->height); 
 | 
  
 | 
    mutex_lock(&isp->subdev_lock); 
 | 
    __isp_subdev_try_format(isp, cfg, fmt); 
 | 
  
 | 
    if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { 
 | 
        mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); 
 | 
        *mf = fmt->format; 
 | 
  
 | 
        /* Propagate format to the source pads */ 
 | 
        if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { 
 | 
            struct v4l2_subdev_format format = *fmt; 
 | 
            unsigned int pad; 
 | 
  
 | 
            for (pad = FIMC_ISP_SD_PAD_SRC_FIFO; 
 | 
                    pad < FIMC_ISP_SD_PADS_NUM; pad++) { 
 | 
                format.pad = pad; 
 | 
                __isp_subdev_try_format(isp, cfg, &format); 
 | 
                mf = v4l2_subdev_get_try_format(sd, cfg, pad); 
 | 
                *mf = format.format; 
 | 
            } 
 | 
        } 
 | 
    } else { 
 | 
        if (sd->entity.stream_count == 0) { 
 | 
            if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { 
 | 
                struct v4l2_subdev_format format = *fmt; 
 | 
  
 | 
                isp->sink_fmt = *mf; 
 | 
  
 | 
                format.pad = FIMC_ISP_SD_PAD_SRC_DMA; 
 | 
                __isp_subdev_try_format(isp, cfg, &format); 
 | 
  
 | 
                isp->src_fmt = format.format; 
 | 
                __is_set_frame_size(is, &isp->src_fmt); 
 | 
            } else { 
 | 
                isp->src_fmt = *mf; 
 | 
            } 
 | 
        } else { 
 | 
            ret = -EBUSY; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    mutex_unlock(&isp->subdev_lock); 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static int fimc_isp_subdev_s_stream(struct v4l2_subdev *sd, int on) 
 | 
{ 
 | 
    struct fimc_isp *isp = v4l2_get_subdevdata(sd); 
 | 
    struct fimc_is *is = fimc_isp_to_is(isp); 
 | 
    int ret; 
 | 
  
 | 
    isp_dbg(1, sd, "%s: on: %d\n", __func__, on); 
 | 
  
 | 
    if (!test_bit(IS_ST_INIT_DONE, &is->state)) 
 | 
        return -EBUSY; 
 | 
  
 | 
    fimc_is_mem_barrier(); 
 | 
  
 | 
    if (on) { 
 | 
        if (__get_pending_param_count(is)) { 
 | 
            ret = fimc_is_itf_s_param(is, true); 
 | 
            if (ret < 0) 
 | 
                return ret; 
 | 
        } 
 | 
  
 | 
        isp_dbg(1, sd, "changing mode to %d\n", is->config_index); 
 | 
  
 | 
        ret = fimc_is_itf_mode_change(is); 
 | 
        if (ret) 
 | 
            return -EINVAL; 
 | 
  
 | 
        clear_bit(IS_ST_STREAM_ON, &is->state); 
 | 
        fimc_is_hw_stream_on(is); 
 | 
        ret = fimc_is_wait_event(is, IS_ST_STREAM_ON, 1, 
 | 
                     FIMC_IS_CONFIG_TIMEOUT); 
 | 
        if (ret < 0) { 
 | 
            v4l2_err(sd, "stream on timeout\n"); 
 | 
            return ret; 
 | 
        } 
 | 
    } else { 
 | 
        clear_bit(IS_ST_STREAM_OFF, &is->state); 
 | 
        fimc_is_hw_stream_off(is); 
 | 
        ret = fimc_is_wait_event(is, IS_ST_STREAM_OFF, 1, 
 | 
                     FIMC_IS_CONFIG_TIMEOUT); 
 | 
        if (ret < 0) { 
 | 
            v4l2_err(sd, "stream off timeout\n"); 
 | 
            return ret; 
 | 
        } 
 | 
        is->setfile.sub_index = 0; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int fimc_isp_subdev_s_power(struct v4l2_subdev *sd, int on) 
 | 
{ 
 | 
    struct fimc_isp *isp = v4l2_get_subdevdata(sd); 
 | 
    struct fimc_is *is = fimc_isp_to_is(isp); 
 | 
    int ret = 0; 
 | 
  
 | 
    pr_debug("on: %d\n", on); 
 | 
  
 | 
    if (on) { 
 | 
        ret = pm_runtime_resume_and_get(&is->pdev->dev); 
 | 
        if (ret < 0) 
 | 
            return ret; 
 | 
  
 | 
        set_bit(IS_ST_PWR_ON, &is->state); 
 | 
  
 | 
        ret = fimc_is_start_firmware(is); 
 | 
        if (ret < 0) { 
 | 
            v4l2_err(sd, "firmware booting failed\n"); 
 | 
            pm_runtime_put(&is->pdev->dev); 
 | 
            return ret; 
 | 
        } 
 | 
        set_bit(IS_ST_PWR_SUBIP_ON, &is->state); 
 | 
  
 | 
        ret = fimc_is_hw_initialize(is); 
 | 
    } else { 
 | 
        /* Close sensor */ 
 | 
        if (!test_bit(IS_ST_PWR_ON, &is->state)) { 
 | 
            fimc_is_hw_close_sensor(is, 0); 
 | 
  
 | 
            ret = fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 0, 
 | 
                         FIMC_IS_CONFIG_TIMEOUT); 
 | 
            if (ret < 0) { 
 | 
                v4l2_err(sd, "sensor close timeout\n"); 
 | 
                return ret; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        /* SUB IP power off */ 
 | 
        if (test_bit(IS_ST_PWR_SUBIP_ON, &is->state)) { 
 | 
            fimc_is_hw_subip_power_off(is); 
 | 
            ret = fimc_is_wait_event(is, IS_ST_PWR_SUBIP_ON, 0, 
 | 
                         FIMC_IS_CONFIG_TIMEOUT); 
 | 
            if (ret < 0) { 
 | 
                v4l2_err(sd, "sub-IP power off timeout\n"); 
 | 
                return ret; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        fimc_is_cpu_set_power(is, 0); 
 | 
        pm_runtime_put_sync(&is->pdev->dev); 
 | 
  
 | 
        clear_bit(IS_ST_PWR_ON, &is->state); 
 | 
        clear_bit(IS_ST_INIT_DONE, &is->state); 
 | 
        is->state = 0; 
 | 
        is->config[is->config_index].p_region_index[0] = 0; 
 | 
        is->config[is->config_index].p_region_index[1] = 0; 
 | 
        set_bit(IS_ST_IDLE, &is->state); 
 | 
        wmb(); 
 | 
    } 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static int fimc_isp_subdev_open(struct v4l2_subdev *sd, 
 | 
                struct v4l2_subdev_fh *fh) 
 | 
{ 
 | 
    struct v4l2_mbus_framefmt *format; 
 | 
    struct v4l2_mbus_framefmt fmt = { 
 | 
        .colorspace = V4L2_COLORSPACE_SRGB, 
 | 
        .code = fimc_isp_formats[0].mbus_code, 
 | 
        .width = DEFAULT_PREVIEW_STILL_WIDTH + FIMC_ISP_CAC_MARGIN_WIDTH, 
 | 
        .height = DEFAULT_PREVIEW_STILL_HEIGHT + FIMC_ISP_CAC_MARGIN_HEIGHT, 
 | 
        .field = V4L2_FIELD_NONE, 
 | 
    }; 
 | 
  
 | 
    format = v4l2_subdev_get_try_format(sd, fh->pad, FIMC_ISP_SD_PAD_SINK); 
 | 
    *format = fmt; 
 | 
  
 | 
    format = v4l2_subdev_get_try_format(sd, fh->pad, FIMC_ISP_SD_PAD_SRC_FIFO); 
 | 
    fmt.width = DEFAULT_PREVIEW_STILL_WIDTH; 
 | 
    fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT; 
 | 
    *format = fmt; 
 | 
  
 | 
    format = v4l2_subdev_get_try_format(sd, fh->pad, FIMC_ISP_SD_PAD_SRC_DMA); 
 | 
    *format = fmt; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int fimc_isp_subdev_registered(struct v4l2_subdev *sd) 
 | 
{ 
 | 
    struct fimc_isp *isp = v4l2_get_subdevdata(sd); 
 | 
    int ret; 
 | 
  
 | 
    /* Use pipeline object allocated by the media device. */ 
 | 
    isp->video_capture.ve.pipe = v4l2_get_subdev_hostdata(sd); 
 | 
  
 | 
    ret = fimc_isp_video_device_register(isp, sd->v4l2_dev, 
 | 
            V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); 
 | 
    if (ret < 0) 
 | 
        isp->video_capture.ve.pipe = NULL; 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static void fimc_isp_subdev_unregistered(struct v4l2_subdev *sd) 
 | 
{ 
 | 
    struct fimc_isp *isp = v4l2_get_subdevdata(sd); 
 | 
  
 | 
    fimc_isp_video_device_unregister(isp, 
 | 
            V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); 
 | 
} 
 | 
  
 | 
static const struct v4l2_subdev_internal_ops fimc_is_subdev_internal_ops = { 
 | 
    .registered = fimc_isp_subdev_registered, 
 | 
    .unregistered = fimc_isp_subdev_unregistered, 
 | 
    .open = fimc_isp_subdev_open, 
 | 
}; 
 | 
  
 | 
static const struct v4l2_subdev_pad_ops fimc_is_subdev_pad_ops = { 
 | 
    .enum_mbus_code = fimc_is_subdev_enum_mbus_code, 
 | 
    .get_fmt = fimc_isp_subdev_get_fmt, 
 | 
    .set_fmt = fimc_isp_subdev_set_fmt, 
 | 
}; 
 | 
  
 | 
static const struct v4l2_subdev_video_ops fimc_is_subdev_video_ops = { 
 | 
    .s_stream = fimc_isp_subdev_s_stream, 
 | 
}; 
 | 
  
 | 
static const struct v4l2_subdev_core_ops fimc_is_core_ops = { 
 | 
    .s_power = fimc_isp_subdev_s_power, 
 | 
}; 
 | 
  
 | 
static const struct v4l2_subdev_ops fimc_is_subdev_ops = { 
 | 
    .core = &fimc_is_core_ops, 
 | 
    .video = &fimc_is_subdev_video_ops, 
 | 
    .pad = &fimc_is_subdev_pad_ops, 
 | 
}; 
 | 
  
 | 
static int __ctrl_set_white_balance(struct fimc_is *is, int value) 
 | 
{ 
 | 
    switch (value) { 
 | 
    case V4L2_WHITE_BALANCE_AUTO: 
 | 
        __is_set_isp_awb(is, ISP_AWB_COMMAND_AUTO, 0); 
 | 
        break; 
 | 
    case V4L2_WHITE_BALANCE_DAYLIGHT: 
 | 
        __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, 
 | 
                    ISP_AWB_ILLUMINATION_DAYLIGHT); 
 | 
        break; 
 | 
    case V4L2_WHITE_BALANCE_CLOUDY: 
 | 
        __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, 
 | 
                    ISP_AWB_ILLUMINATION_CLOUDY); 
 | 
        break; 
 | 
    case V4L2_WHITE_BALANCE_INCANDESCENT: 
 | 
        __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, 
 | 
                    ISP_AWB_ILLUMINATION_TUNGSTEN); 
 | 
        break; 
 | 
    case V4L2_WHITE_BALANCE_FLUORESCENT: 
 | 
        __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, 
 | 
                    ISP_AWB_ILLUMINATION_FLUORESCENT); 
 | 
        break; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int __ctrl_set_aewb_lock(struct fimc_is *is, 
 | 
                      struct v4l2_ctrl *ctrl) 
 | 
{ 
 | 
    bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE; 
 | 
    bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE; 
 | 
    struct isp_param *isp = &is->is_p_region->parameter.isp; 
 | 
    int cmd, ret; 
 | 
  
 | 
    cmd = ae_lock ? ISP_AA_COMMAND_STOP : ISP_AA_COMMAND_START; 
 | 
    isp->aa.cmd = cmd; 
 | 
    isp->aa.target = ISP_AA_TARGET_AE; 
 | 
    fimc_is_set_param_bit(is, PARAM_ISP_AA); 
 | 
    is->af.ae_lock_state = ae_lock; 
 | 
    wmb(); 
 | 
  
 | 
    ret = fimc_is_itf_s_param(is, false); 
 | 
    if (ret < 0) 
 | 
        return ret; 
 | 
  
 | 
    cmd = awb_lock ? ISP_AA_COMMAND_STOP : ISP_AA_COMMAND_START; 
 | 
    isp->aa.cmd = cmd; 
 | 
    isp->aa.target = ISP_AA_TARGET_AE; 
 | 
    fimc_is_set_param_bit(is, PARAM_ISP_AA); 
 | 
    is->af.awb_lock_state = awb_lock; 
 | 
    wmb(); 
 | 
  
 | 
    return fimc_is_itf_s_param(is, false); 
 | 
} 
 | 
  
 | 
/* Supported manual ISO values */ 
 | 
static const s64 iso_qmenu[] = { 
 | 
    50, 100, 200, 400, 800, 
 | 
}; 
 | 
  
 | 
static int __ctrl_set_iso(struct fimc_is *is, int value) 
 | 
{ 
 | 
    unsigned int idx, iso; 
 | 
  
 | 
    if (value == V4L2_ISO_SENSITIVITY_AUTO) { 
 | 
        __is_set_isp_iso(is, ISP_ISO_COMMAND_AUTO, 0); 
 | 
        return 0; 
 | 
    } 
 | 
    idx = is->isp.ctrls.iso->val; 
 | 
    if (idx >= ARRAY_SIZE(iso_qmenu)) 
 | 
        return -EINVAL; 
 | 
  
 | 
    iso = iso_qmenu[idx]; 
 | 
    __is_set_isp_iso(is, ISP_ISO_COMMAND_MANUAL, iso); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int __ctrl_set_metering(struct fimc_is *is, unsigned int value) 
 | 
{ 
 | 
    unsigned int val; 
 | 
  
 | 
    switch (value) { 
 | 
    case V4L2_EXPOSURE_METERING_AVERAGE: 
 | 
        val = ISP_METERING_COMMAND_AVERAGE; 
 | 
        break; 
 | 
    case V4L2_EXPOSURE_METERING_CENTER_WEIGHTED: 
 | 
        val = ISP_METERING_COMMAND_CENTER; 
 | 
        break; 
 | 
    case V4L2_EXPOSURE_METERING_SPOT: 
 | 
        val = ISP_METERING_COMMAND_SPOT; 
 | 
        break; 
 | 
    case V4L2_EXPOSURE_METERING_MATRIX: 
 | 
        val = ISP_METERING_COMMAND_MATRIX; 
 | 
        break; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    __is_set_isp_metering(is, IS_METERING_CONFIG_CMD, val); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int __ctrl_set_afc(struct fimc_is *is, int value) 
 | 
{ 
 | 
    switch (value) { 
 | 
    case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: 
 | 
        __is_set_isp_afc(is, ISP_AFC_COMMAND_DISABLE, 0); 
 | 
        break; 
 | 
    case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: 
 | 
        __is_set_isp_afc(is, ISP_AFC_COMMAND_MANUAL, 50); 
 | 
        break; 
 | 
    case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: 
 | 
        __is_set_isp_afc(is, ISP_AFC_COMMAND_MANUAL, 60); 
 | 
        break; 
 | 
    case V4L2_CID_POWER_LINE_FREQUENCY_AUTO: 
 | 
        __is_set_isp_afc(is, ISP_AFC_COMMAND_AUTO, 0); 
 | 
        break; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int __ctrl_set_image_effect(struct fimc_is *is, int value) 
 | 
{ 
 | 
    static const u8 effects[][2] = { 
 | 
        { V4L2_COLORFX_NONE,     ISP_IMAGE_EFFECT_DISABLE }, 
 | 
        { V4L2_COLORFX_BW,     ISP_IMAGE_EFFECT_MONOCHROME }, 
 | 
        { V4L2_COLORFX_SEPIA,     ISP_IMAGE_EFFECT_SEPIA }, 
 | 
        { V4L2_COLORFX_NEGATIVE, ISP_IMAGE_EFFECT_NEGATIVE_MONO }, 
 | 
        { 16 /* TODO */,     ISP_IMAGE_EFFECT_NEGATIVE_COLOR }, 
 | 
    }; 
 | 
    int i; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(effects); i++) { 
 | 
        if (effects[i][0] != value) 
 | 
            continue; 
 | 
  
 | 
        __is_set_isp_effect(is, effects[i][1]); 
 | 
        return 0; 
 | 
    } 
 | 
  
 | 
    return -EINVAL; 
 | 
} 
 | 
  
 | 
static int fimc_is_s_ctrl(struct v4l2_ctrl *ctrl) 
 | 
{ 
 | 
    struct fimc_isp *isp = ctrl_to_fimc_isp(ctrl); 
 | 
    struct fimc_is *is = fimc_isp_to_is(isp); 
 | 
    bool set_param = true; 
 | 
    int ret = 0; 
 | 
  
 | 
    switch (ctrl->id) { 
 | 
    case V4L2_CID_CONTRAST: 
 | 
        __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_CONTRAST, 
 | 
                    ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_SATURATION: 
 | 
        __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SATURATION, 
 | 
                    ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_SHARPNESS: 
 | 
        __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SHARPNESS, 
 | 
                    ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_EXPOSURE_ABSOLUTE: 
 | 
        __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_EXPOSURE, 
 | 
                    ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_BRIGHTNESS: 
 | 
        __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS, 
 | 
                    ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_HUE: 
 | 
        __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_HUE, 
 | 
                    ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_EXPOSURE_METERING: 
 | 
        ret = __ctrl_set_metering(is, ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: 
 | 
        ret = __ctrl_set_white_balance(is, ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_3A_LOCK: 
 | 
        ret = __ctrl_set_aewb_lock(is, ctrl); 
 | 
        set_param = false; 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_ISO_SENSITIVITY_AUTO: 
 | 
        ret = __ctrl_set_iso(is, ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_POWER_LINE_FREQUENCY: 
 | 
        ret = __ctrl_set_afc(is, ctrl->val); 
 | 
        break; 
 | 
  
 | 
    case V4L2_CID_COLORFX: 
 | 
        __ctrl_set_image_effect(is, ctrl->val); 
 | 
        break; 
 | 
  
 | 
    default: 
 | 
        ret = -EINVAL; 
 | 
        break; 
 | 
    } 
 | 
  
 | 
    if (ret < 0) { 
 | 
        v4l2_err(&isp->subdev, "Failed to set control: %s (%d)\n", 
 | 
                        ctrl->name, ctrl->val); 
 | 
        return ret; 
 | 
    } 
 | 
  
 | 
    if (set_param && test_bit(IS_ST_STREAM_ON, &is->state)) 
 | 
        return fimc_is_itf_s_param(is, true); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct v4l2_ctrl_ops fimc_isp_ctrl_ops = { 
 | 
    .s_ctrl    = fimc_is_s_ctrl, 
 | 
}; 
 | 
  
 | 
static void __isp_subdev_set_default_format(struct fimc_isp *isp) 
 | 
{ 
 | 
    struct fimc_is *is = fimc_isp_to_is(isp); 
 | 
  
 | 
    isp->sink_fmt.width = DEFAULT_PREVIEW_STILL_WIDTH + 
 | 
                FIMC_ISP_CAC_MARGIN_WIDTH; 
 | 
    isp->sink_fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT + 
 | 
                FIMC_ISP_CAC_MARGIN_HEIGHT; 
 | 
    isp->sink_fmt.code = MEDIA_BUS_FMT_SGRBG10_1X10; 
 | 
  
 | 
    isp->src_fmt.width = DEFAULT_PREVIEW_STILL_WIDTH; 
 | 
    isp->src_fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT; 
 | 
    isp->src_fmt.code = MEDIA_BUS_FMT_SGRBG10_1X10; 
 | 
    __is_set_frame_size(is, &isp->src_fmt); 
 | 
} 
 | 
  
 | 
int fimc_isp_subdev_create(struct fimc_isp *isp) 
 | 
{ 
 | 
    const struct v4l2_ctrl_ops *ops = &fimc_isp_ctrl_ops; 
 | 
    struct v4l2_ctrl_handler *handler = &isp->ctrls.handler; 
 | 
    struct v4l2_subdev *sd = &isp->subdev; 
 | 
    struct fimc_isp_ctrls *ctrls = &isp->ctrls; 
 | 
    int ret; 
 | 
  
 | 
    mutex_init(&isp->subdev_lock); 
 | 
  
 | 
    v4l2_subdev_init(sd, &fimc_is_subdev_ops); 
 | 
  
 | 
    sd->owner = THIS_MODULE; 
 | 
    sd->grp_id = GRP_ID_FIMC_IS; 
 | 
    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 
 | 
    snprintf(sd->name, sizeof(sd->name), "FIMC-IS-ISP"); 
 | 
  
 | 
    sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; 
 | 
    isp->subdev_pads[FIMC_ISP_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK; 
 | 
    isp->subdev_pads[FIMC_ISP_SD_PAD_SRC_FIFO].flags = MEDIA_PAD_FL_SOURCE; 
 | 
    isp->subdev_pads[FIMC_ISP_SD_PAD_SRC_DMA].flags = MEDIA_PAD_FL_SOURCE; 
 | 
    ret = media_entity_pads_init(&sd->entity, FIMC_ISP_SD_PADS_NUM, 
 | 
                isp->subdev_pads); 
 | 
    if (ret) 
 | 
        return ret; 
 | 
  
 | 
    v4l2_ctrl_handler_init(handler, 20); 
 | 
  
 | 
    ctrls->saturation = v4l2_ctrl_new_std(handler, ops, V4L2_CID_SATURATION, 
 | 
                        -2, 2, 1, 0); 
 | 
    ctrls->brightness = v4l2_ctrl_new_std(handler, ops, V4L2_CID_BRIGHTNESS, 
 | 
                        -4, 4, 1, 0); 
 | 
    ctrls->contrast = v4l2_ctrl_new_std(handler, ops, V4L2_CID_CONTRAST, 
 | 
                        -2, 2, 1, 0); 
 | 
    ctrls->sharpness = v4l2_ctrl_new_std(handler, ops, V4L2_CID_SHARPNESS, 
 | 
                        -2, 2, 1, 0); 
 | 
    ctrls->hue = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HUE, 
 | 
                        -2, 2, 1, 0); 
 | 
  
 | 
    ctrls->auto_wb = v4l2_ctrl_new_std_menu(handler, ops, 
 | 
                    V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, 
 | 
                    8, ~0x14e, V4L2_WHITE_BALANCE_AUTO); 
 | 
  
 | 
    ctrls->exposure = v4l2_ctrl_new_std(handler, ops, 
 | 
                    V4L2_CID_EXPOSURE_ABSOLUTE, 
 | 
                    -4, 4, 1, 0); 
 | 
  
 | 
    ctrls->exp_metering = v4l2_ctrl_new_std_menu(handler, ops, 
 | 
                    V4L2_CID_EXPOSURE_METERING, 3, 
 | 
                    ~0xf, V4L2_EXPOSURE_METERING_AVERAGE); 
 | 
  
 | 
    v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_POWER_LINE_FREQUENCY, 
 | 
                    V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, 
 | 
                    V4L2_CID_POWER_LINE_FREQUENCY_AUTO); 
 | 
    /* ISO sensitivity */ 
 | 
    ctrls->auto_iso = v4l2_ctrl_new_std_menu(handler, ops, 
 | 
            V4L2_CID_ISO_SENSITIVITY_AUTO, 1, 0, 
 | 
            V4L2_ISO_SENSITIVITY_AUTO); 
 | 
  
 | 
    ctrls->iso = v4l2_ctrl_new_int_menu(handler, ops, 
 | 
            V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1, 
 | 
            ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu); 
 | 
  
 | 
    ctrls->aewb_lock = v4l2_ctrl_new_std(handler, ops, 
 | 
                    V4L2_CID_3A_LOCK, 0, 0x3, 0, 0); 
 | 
  
 | 
    /* TODO: Add support for NEGATIVE_COLOR option */ 
 | 
    ctrls->colorfx = v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_COLORFX, 
 | 
            V4L2_COLORFX_SET_CBCR + 1, ~0x1000f, V4L2_COLORFX_NONE); 
 | 
  
 | 
    if (handler->error) { 
 | 
        media_entity_cleanup(&sd->entity); 
 | 
        return handler->error; 
 | 
    } 
 | 
  
 | 
    v4l2_ctrl_auto_cluster(2, &ctrls->auto_iso, 
 | 
            V4L2_ISO_SENSITIVITY_MANUAL, false); 
 | 
  
 | 
    sd->ctrl_handler = handler; 
 | 
    sd->internal_ops = &fimc_is_subdev_internal_ops; 
 | 
    sd->entity.ops = &fimc_is_subdev_media_ops; 
 | 
    v4l2_set_subdevdata(sd, isp); 
 | 
  
 | 
    __isp_subdev_set_default_format(isp); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
void fimc_isp_subdev_destroy(struct fimc_isp *isp) 
 | 
{ 
 | 
    struct v4l2_subdev *sd = &isp->subdev; 
 | 
  
 | 
    v4l2_device_unregister_subdev(sd); 
 | 
    media_entity_cleanup(&sd->entity); 
 | 
    v4l2_ctrl_handler_free(&isp->ctrls.handler); 
 | 
    v4l2_set_subdevdata(sd, NULL); 
 | 
} 
 |