diff options
Diffstat (limited to 'drivers/media/video/s5k4ecgx.c')
-rwxr-xr-x | drivers/media/video/s5k4ecgx.c | 2837 |
1 files changed, 2837 insertions, 0 deletions
diff --git a/drivers/media/video/s5k4ecgx.c b/drivers/media/video/s5k4ecgx.c new file mode 100755 index 0000000..9d9d5b4 --- /dev/null +++ b/drivers/media/video/s5k4ecgx.c @@ -0,0 +1,2837 @@ +/* drivers/media/video/s5k4ecgx.c + * + * Driver for s5k4ecgx (5MP Camera) from SEC + * + * Copyright (C) 2010, SAMSUNG ELECTRONICS + * + * 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 <linux/i2c.h> +#include <linux/delay.h> +#include <linux/version.h> +#include <linux/vmalloc.h> +#include <linux/completion.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/s5k4ecgx.h> +#include <linux/videodev2_samsung.h> + +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_0 +#include "s5k4ecgx_regs_1_0.h" +#define S5K4ECGX_VERSION_1_0 0x00 +#endif + +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_1 +#include "s5k4ecgx_regs_1_1.h" +#define S5K4ECGX_VERSION_1_1 0x11 +#endif + +#define FORMAT_FLAGS_COMPRESSED 0x3 +#define SENSOR_JPEG_SNAPSHOT_MEMSIZE 0x410580 + +#define DEFAULT_PIX_FMT V4L2_PIX_FMT_UYVY /* YUV422 */ +#define DEFAULT_MCLK 24000000 +#define POLL_TIME_MS 10 +#define CAPTURE_POLL_TIME_MS 1000 + +/* maximum time for one frame at minimum fps (15fps) in normal mode */ +#define NORMAL_MODE_MAX_ONE_FRAME_DELAY_MS 67 +/* maximum time for one frame at minimum fps (4fps) in night mode */ +#define NIGHT_MODE_MAX_ONE_FRAME_DELAY_MS 250 + +/* time to move lens to target position before last af mode register write */ +#define LENS_MOVE_TIME_MS 100 + +/* level at or below which we need to enable flash when in auto mode */ +#define LOW_LIGHT_LEVEL 0x1D + +/* level at or below which we need to use low light capture mode */ +#define HIGH_LIGHT_LEVEL 0x80 + +#define FIRST_AF_SEARCH_COUNT 80 +#define SECOND_AF_SEARCH_COUNT 80 +#define AE_STABLE_SEARCH_COUNT 4 + +#define FIRST_SETTING_FOCUS_MODE_DELAY_MS 100 +#define SECOND_SETTING_FOCUS_MODE_DELAY_MS 200 + +#ifdef CONFIG_VIDEO_S5K4ECGX_DEBUG +enum { + S5K4ECGX_DEBUG_I2C = 1U << 0, + S5K4ECGX_DEBUG_I2C_BURSTS = 1U << 1, +}; +static uint32_t s5k4ecgx_debug_mask = S5K4ECGX_DEBUG_I2C_BURSTS; +module_param_named(debug_mask, s5k4ecgx_debug_mask, uint, S_IWUSR | S_IRUGO); + +#define s5k4ecgx_debug(mask, x...) \ + do { \ + if (s5k4ecgx_debug_mask & mask) \ + pr_info(x); \ + } while (0) +#else + +#define s5k4ecgx_debug(mask, x...) + +#endif + +#define S5K4ECGX_VERSION_1_1 0x11 + +/* result values returned to HAL */ +enum { + AUTO_FOCUS_FAILED, + AUTO_FOCUS_DONE, + AUTO_FOCUS_CANCELLED, +}; + +enum af_operation_status { + AF_NONE = 0, + AF_START, + AF_CANCEL, + AF_INITIAL, +}; + +enum s5k4ecgx_oprmode { + S5K4ECGX_OPRMODE_VIDEO = 0, + S5K4ECGX_OPRMODE_IMAGE = 1, +}; + +enum s5k4ecgx_preview_frame_size { + S5K4ECGX_PREVIEW_QCIF = 0, /* 176x144 */ + S5K4ECGX_PREVIEW_CIF, /* 352x288 */ + S5K4ECGX_PREVIEW_VGA, /* 640x480 */ + S5K4ECGX_PREVIEW_D1, /* 720x480 */ + S5K4ECGX_PREVIEW_WVGA, /* 800x480 */ + S5K4ECGX_PREVIEW_SVGA, /* 800x600 */ + S5K4ECGX_PREVIEW_WSVGA, /* 1024x600*/ + S5K4ECGX_PREVIEW_MAX, +}; + +enum s5k4ecgx_capture_frame_size { + S5K4ECGX_CAPTURE_VGA = 0, /* 640x480 */ + S5K4ECGX_CAPTURE_WVGA, /* 800x480 */ + S5K4ECGX_CAPTURE_SVGA, /* 800x600 */ + S5K4ECGX_CAPTURE_WSVGA, /* 1024x600 */ + S5K4ECGX_CAPTURE_1MP, /* 1280x960 */ + S5K4ECGX_CAPTURE_W1MP, /* 1600x960 */ + S5K4ECGX_CAPTURE_2MP, /* UXGA - 1600x1200 */ + S5K4ECGX_CAPTURE_W2MP, /* 35mm Academy Offset Standard 1.66 */ + /* 2048x1232, 2.4MP */ + S5K4ECGX_CAPTURE_3MP, /* QXGA - 2048x1536 */ + S5K4ECGX_CAPTURE_W4MP, /* WQXGA - 2560x1536 */ + S5K4ECGX_CAPTURE_5MP, /* 2560x1920 */ + S5K4ECGX_CAPTURE_MAX, +}; + +struct s5k4ecgx_framesize { + u32 index; + u32 width; + u32 height; +}; + +static const struct s5k4ecgx_framesize s5k4ecgx_preview_framesize_list[] = { + { S5K4ECGX_PREVIEW_QCIF, 176, 144 }, + { S5K4ECGX_PREVIEW_CIF, 352, 288 }, + { S5K4ECGX_PREVIEW_VGA, 640, 480 }, + { S5K4ECGX_PREVIEW_D1, 720, 480 }, +}; + +static const struct s5k4ecgx_framesize s5k4ecgx_capture_framesize_list[] = { + { S5K4ECGX_CAPTURE_VGA, 640, 480 }, + { S5K4ECGX_CAPTURE_1MP, 1280, 960 }, + { S5K4ECGX_CAPTURE_2MP, 1600, 1200 }, + { S5K4ECGX_CAPTURE_3MP, 2048, 1536 }, + { S5K4ECGX_CAPTURE_5MP, 2560, 1920 }, +}; + +struct s5k4ecgx_version { + u32 major; + u32 minor; +}; + +struct s5k4ecgx_date_info { + u32 year; + u32 month; + u32 date; +}; + +enum s5k4ecgx_runmode { + S5K4ECGX_RUNMODE_NOTREADY, + S5K4ECGX_RUNMODE_IDLE, + S5K4ECGX_RUNMODE_RUNNING, + S5K4ECGX_RUNMODE_CAPTURE, +}; + +struct s5k4ecgx_firmware { + u32 addr; + u32 size; +}; + +struct s5k4ecgx_jpeg_param { + u32 enable; + u32 quality; + u32 main_size; /* Main JPEG file size */ + u32 thumb_size; /* Thumbnail file size */ + u32 main_offset; + u32 thumb_offset; + u32 postview_offset; +}; + +struct s5k4ecgx_position { + int x; + int y; +}; + +struct gps_info_common { + u32 direction; + u32 dgree; + u32 minute; + u32 second; +}; + +struct s5k4ecgx_gps_info { + unsigned char gps_buf[8]; + unsigned char altitude_buf[4]; + int gps_timeStamp; +}; + +struct s5k4ecgx_regset { + u32 size; + u8 *data; +}; + +struct s5k4ecgx_regset_table { + const u32 *reg; + int array_size; +}; + +#define S5K4ECGX_REGSET(x, y) \ + [(x)] = { \ + .reg = (y), \ + .array_size = ARRAY_SIZE((y)), \ +} + +#define S5K4ECGX_REGSET_TABLE(y) \ + { \ + .reg = (y), \ + .array_size = ARRAY_SIZE((y)), \ +} + +struct s5k4ecgx_regs { + struct s5k4ecgx_regset_table ev[EV_MAX]; + struct s5k4ecgx_regset_table metering[METERING_MAX]; + struct s5k4ecgx_regset_table iso[ISO_MAX]; + struct s5k4ecgx_regset_table effect[IMAGE_EFFECT_MAX]; + struct s5k4ecgx_regset_table white_balance[WHITE_BALANCE_MAX]; + struct s5k4ecgx_regset_table preview_size[S5K4ECGX_PREVIEW_MAX]; + struct s5k4ecgx_regset_table capture_size[S5K4ECGX_CAPTURE_MAX]; + struct s5k4ecgx_regset_table scene_mode[SCENE_MODE_MAX]; + struct s5k4ecgx_regset_table saturation[SATURATION_MAX]; + struct s5k4ecgx_regset_table contrast[CONTRAST_MAX]; + struct s5k4ecgx_regset_table sharpness[SHARPNESS_MAX]; + struct s5k4ecgx_regset_table fps[FRAME_RATE_MAX]; + struct s5k4ecgx_regset_table preview_return; + struct s5k4ecgx_regset_table jpeg_quality_high; + struct s5k4ecgx_regset_table jpeg_quality_normal; + struct s5k4ecgx_regset_table jpeg_quality_low; + struct s5k4ecgx_regset_table flash_start; + struct s5k4ecgx_regset_table flash_end; + struct s5k4ecgx_regset_table af_assist_flash_start; + struct s5k4ecgx_regset_table af_assist_flash_end; + struct s5k4ecgx_regset_table af_low_light_mode_on; + struct s5k4ecgx_regset_table af_low_light_mode_off; + struct s5k4ecgx_regset_table ae_awb_lock_on; + struct s5k4ecgx_regset_table ae_awb_lock_off; + struct s5k4ecgx_regset_table low_cap_on; + struct s5k4ecgx_regset_table low_cap_off; + struct s5k4ecgx_regset_table wdr_on; + struct s5k4ecgx_regset_table wdr_off; + struct s5k4ecgx_regset_table face_detection_on; + struct s5k4ecgx_regset_table face_detection_off; + struct s5k4ecgx_regset_table capture_start; + struct s5k4ecgx_regset_table af_macro_mode_1; + struct s5k4ecgx_regset_table af_macro_mode_2; + struct s5k4ecgx_regset_table af_macro_mode_3; + struct s5k4ecgx_regset_table af_normal_mode_1; + struct s5k4ecgx_regset_table af_normal_mode_2; + struct s5k4ecgx_regset_table af_normal_mode_3; + struct s5k4ecgx_regset_table af_return_macro_position; + struct s5k4ecgx_regset_table single_af_start; + struct s5k4ecgx_regset_table single_af_off_1; + struct s5k4ecgx_regset_table single_af_off_2; + struct s5k4ecgx_regset_table dtp_start; + struct s5k4ecgx_regset_table dtp_stop; + struct s5k4ecgx_regset_table init_reg_1; + struct s5k4ecgx_regset_table init_reg_2; + struct s5k4ecgx_regset_table flash_init; + struct s5k4ecgx_regset_table reset_crop; + struct s5k4ecgx_regset_table get_ae_stable_status; + struct s5k4ecgx_regset_table get_light_level; + struct s5k4ecgx_regset_table get_1st_af_search_status; + struct s5k4ecgx_regset_table get_2nd_af_search_status; + struct s5k4ecgx_regset_table get_capture_status; + struct s5k4ecgx_regset_table get_esd_status; + struct s5k4ecgx_regset_table get_iso; + struct s5k4ecgx_regset_table get_shutterspeed; +}; + +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_0 +static const struct s5k4ecgx_regs regs_for_fw_version_1_0 = { + .ev = { + S5K4ECGX_REGSET(EV_MINUS_4, s5k4ecgx_EV_Minus_4_v1), + S5K4ECGX_REGSET(EV_MINUS_3, s5k4ecgx_EV_Minus_3_v1), + S5K4ECGX_REGSET(EV_MINUS_2, s5k4ecgx_EV_Minus_2_v1), + S5K4ECGX_REGSET(EV_MINUS_1, s5k4ecgx_EV_Minus_1_v1), + S5K4ECGX_REGSET(EV_DEFAULT, s5k4ecgx_EV_Default_v1), + S5K4ECGX_REGSET(EV_PLUS_1, s5k4ecgx_EV_Plus_1_v1), + S5K4ECGX_REGSET(EV_PLUS_2, s5k4ecgx_EV_Plus_2_v1), + S5K4ECGX_REGSET(EV_PLUS_3, s5k4ecgx_EV_Plus_3_v1), + S5K4ECGX_REGSET(EV_PLUS_4, s5k4ecgx_EV_Plus_4_v1), + }, + .metering = { + S5K4ECGX_REGSET(METERING_MATRIX, s5k4ecgx_Metering_Matrix_v1), + S5K4ECGX_REGSET(METERING_CENTER, s5k4ecgx_Metering_Center_v1), + S5K4ECGX_REGSET(METERING_SPOT, s5k4ecgx_Metering_Spot_v1), + }, + .iso = { + S5K4ECGX_REGSET(ISO_AUTO, s5k4ecgx_ISO_Auto_v1), + S5K4ECGX_REGSET(ISO_50, s5k4ecgx_ISO_100_v1), /* use 100 */ + S5K4ECGX_REGSET(ISO_100, s5k4ecgx_ISO_100_v1), + S5K4ECGX_REGSET(ISO_200, s5k4ecgx_ISO_200_v1), + S5K4ECGX_REGSET(ISO_400, s5k4ecgx_ISO_400_v1), + S5K4ECGX_REGSET(ISO_800, s5k4ecgx_ISO_400_v1), /* use 400 */ + S5K4ECGX_REGSET(ISO_1600, s5k4ecgx_ISO_400_v1), /* use 400 */ + S5K4ECGX_REGSET(ISO_SPORTS, s5k4ecgx_ISO_Auto_v1),/* use auto */ + S5K4ECGX_REGSET(ISO_NIGHT, s5k4ecgx_ISO_Auto_v1), /* use auto */ + S5K4ECGX_REGSET(ISO_MOVIE, s5k4ecgx_ISO_Auto_v1), /* use auto */ + }, + .effect = { + S5K4ECGX_REGSET(IMAGE_EFFECT_NONE, s5k4ecgx_Effect_Normal_v1), + S5K4ECGX_REGSET(IMAGE_EFFECT_BNW, + s5k4ecgx_Effect_Black_White_v1), + S5K4ECGX_REGSET(IMAGE_EFFECT_SEPIA, s5k4ecgx_Effect_Sepia_v1), + S5K4ECGX_REGSET(IMAGE_EFFECT_NEGATIVE, + s5k4ecgx_Effect_Negative_v1), + }, + .white_balance = { + S5K4ECGX_REGSET(WHITE_BALANCE_AUTO, s5k4ecgx_WB_Auto_v1), + S5K4ECGX_REGSET(WHITE_BALANCE_SUNNY, s5k4ecgx_WB_Sunny_v1), + S5K4ECGX_REGSET(WHITE_BALANCE_CLOUDY, s5k4ecgx_WB_Cloudy_v1), + S5K4ECGX_REGSET(WHITE_BALANCE_TUNGSTEN, + s5k4ecgx_WB_Tungsten_v1), + S5K4ECGX_REGSET(WHITE_BALANCE_FLUORESCENT, + s5k4ecgx_WB_Fluorescent_v1), + }, + .preview_size = { + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_QCIF, s5k4ecgx_176_Preview_v1), + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_CIF, s5k4ecgx_352_Preview_v1), + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_VGA, s5k4ecgx_640_Preview_v1), + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_D1, s5k4ecgx_720_Preview_v1), + }, + .capture_size = { + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_VGA, s5k4ecgx_VGA_Capture_v1), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_1MP, s5k4ecgx_1M_Capture_v1), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_2MP, s5k4ecgx_2M_Capture_v1), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_3MP, s5k4ecgx_3M_Capture_v1), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_5MP, s5k4ecgx_5M_Capture_v1), + }, + .scene_mode = { + S5K4ECGX_REGSET(SCENE_MODE_NONE, s5k4ecgx_Scene_Default_v1), + S5K4ECGX_REGSET(SCENE_MODE_PORTRAIT, + s5k4ecgx_Scene_Portrait_v1), + S5K4ECGX_REGSET(SCENE_MODE_NIGHTSHOT, + s5k4ecgx_Scene_Nightshot_v1), + S5K4ECGX_REGSET(SCENE_MODE_LANDSCAPE, + s5k4ecgx_Scene_Landscape_v1), + S5K4ECGX_REGSET(SCENE_MODE_SPORTS, s5k4ecgx_Scene_Sports_v1), + S5K4ECGX_REGSET(SCENE_MODE_PARTY_INDOOR, + s5k4ecgx_Scene_Party_Indoor_v1), + S5K4ECGX_REGSET(SCENE_MODE_BEACH_SNOW, + s5k4ecgx_Scene_Beach_Snow_v1), + S5K4ECGX_REGSET(SCENE_MODE_SUNSET, s5k4ecgx_Scene_Sunset_v1), + S5K4ECGX_REGSET(SCENE_MODE_FIREWORKS, + s5k4ecgx_Scene_Fireworks_v1), + S5K4ECGX_REGSET(SCENE_MODE_CANDLE_LIGHT, + s5k4ecgx_Scene_Candle_Light_v1), + }, + .saturation = { + S5K4ECGX_REGSET(SATURATION_MINUS_2, + s5k4ecgx_Saturation_Minus_2_v1), + S5K4ECGX_REGSET(SATURATION_MINUS_1, + s5k4ecgx_Saturation_Minus_1_v1), + S5K4ECGX_REGSET(SATURATION_DEFAULT, + s5k4ecgx_Saturation_Default_v1), + S5K4ECGX_REGSET(SATURATION_PLUS_1, + s5k4ecgx_Saturation_Plus_1_v1), + S5K4ECGX_REGSET(SATURATION_PLUS_2, + s5k4ecgx_Saturation_Plus_2_v1), + }, + .contrast = { + S5K4ECGX_REGSET(CONTRAST_MINUS_2, s5k4ecgx_Contrast_Minus_2_v1), + S5K4ECGX_REGSET(CONTRAST_MINUS_1, s5k4ecgx_Contrast_Minus_1_v1), + S5K4ECGX_REGSET(CONTRAST_DEFAULT, s5k4ecgx_Contrast_Default_v1), + S5K4ECGX_REGSET(CONTRAST_PLUS_1, s5k4ecgx_Contrast_Plus_1_v1), + S5K4ECGX_REGSET(CONTRAST_PLUS_2, s5k4ecgx_Contrast_Plus_2_v1), + }, + .sharpness = { + S5K4ECGX_REGSET(SHARPNESS_MINUS_2, + s5k4ecgx_Sharpness_Minus_2_v1), + S5K4ECGX_REGSET(SHARPNESS_MINUS_1, + s5k4ecgx_Sharpness_Minus_1_v1), + S5K4ECGX_REGSET(SHARPNESS_DEFAULT, + s5k4ecgx_Sharpness_Default_v1), + S5K4ECGX_REGSET(SHARPNESS_PLUS_1, s5k4ecgx_Sharpness_Plus_1_v1), + S5K4ECGX_REGSET(SHARPNESS_PLUS_2, s5k4ecgx_Sharpness_Plus_2_v1), + }, + .preview_return = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Preview_Return_v1), + .jpeg_quality_high = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Jpeg_Quality_High_v1), + .jpeg_quality_normal = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Jpeg_Quality_Normal_v1), + .jpeg_quality_low = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Jpeg_Quality_Low_v1), + .flash_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Flash_Start_v1), + .flash_end = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Flash_End_v1), + .af_assist_flash_start = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Pre_Flash_Start_v1), + .af_assist_flash_end = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Pre_Flash_End_v1), + .af_low_light_mode_on = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Low_Light_Mode_On_v1), + .af_low_light_mode_off = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Low_Light_Mode_Off_v1), + .ae_awb_lock_on = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AE_AWB_Lock_On_v1), + .ae_awb_lock_off = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AE_AWB_Lock_Off_v1), + .low_cap_on = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Low_Cap_On_v1), + .low_cap_off = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Low_Cap_Off_v1), + .wdr_on = S5K4ECGX_REGSET_TABLE(s5k4ecgx_WDR_on_v1), + .wdr_off = S5K4ECGX_REGSET_TABLE(s5k4ecgx_WDR_off_v1), + .face_detection_on = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Face_Detection_On_v1), + .face_detection_off = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Face_Detection_Off_v1), + .capture_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Capture_Start_v1), + .af_macro_mode_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Macro_mode_1_v1), + .af_macro_mode_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Macro_mode_2_v1), + .af_macro_mode_3 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Macro_mode_3_v1), + .af_normal_mode_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Normal_mode_1_v1), + .af_normal_mode_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Normal_mode_2_v1), + .af_normal_mode_3 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Normal_mode_3_v1), + .af_return_macro_position = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Return_Macro_pos_v1), + .single_af_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Single_AF_Start_v1), + .single_af_off_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Single_AF_Off_1_v1), + .single_af_off_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Single_AF_Off_2_v1), + .dtp_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_DTP_init_v1), + .dtp_stop = S5K4ECGX_REGSET_TABLE(s5k4ecgx_DTP_stop_v1), + .init_reg_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_init_reg1_v1), + .init_reg_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_init_reg2_v1), + .flash_init = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Flash_init_v1), + .reset_crop = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Reset_Crop_v1), + .get_ae_stable_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Get_AE_Stable_Status_v1), + .get_light_level = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Get_Light_Level_v1), + .get_1st_af_search_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_1st_af_search_status_v1), + .get_2nd_af_search_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_2nd_af_search_status_v1), + .get_capture_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_capture_status_v1), + .get_esd_status = S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_esd_status_v1), + .get_iso = S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_iso_reg_v1), + .get_shutterspeed = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_shutterspeed_reg_v1), +}; +#endif + +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_1 +static const struct s5k4ecgx_regs regs_for_fw_version_1_1 = { + .ev = { + S5K4ECGX_REGSET(EV_MINUS_4, s5k4ecgx_EV_Minus_4), + S5K4ECGX_REGSET(EV_MINUS_3, s5k4ecgx_EV_Minus_3), + S5K4ECGX_REGSET(EV_MINUS_2, s5k4ecgx_EV_Minus_2), + S5K4ECGX_REGSET(EV_MINUS_1, s5k4ecgx_EV_Minus_1), + S5K4ECGX_REGSET(EV_DEFAULT, s5k4ecgx_EV_Default), + S5K4ECGX_REGSET(EV_PLUS_1, s5k4ecgx_EV_Plus_1), + S5K4ECGX_REGSET(EV_PLUS_2, s5k4ecgx_EV_Plus_2), + S5K4ECGX_REGSET(EV_PLUS_3, s5k4ecgx_EV_Plus_3), + S5K4ECGX_REGSET(EV_PLUS_4, s5k4ecgx_EV_Plus_4), + }, + .metering = { + S5K4ECGX_REGSET(METERING_MATRIX, s5k4ecgx_Metering_Matrix), + S5K4ECGX_REGSET(METERING_CENTER, s5k4ecgx_Metering_Center), + S5K4ECGX_REGSET(METERING_SPOT, s5k4ecgx_Metering_Spot), + }, + .iso = { + S5K4ECGX_REGSET(ISO_AUTO, s5k4ecgx_ISO_Auto), + S5K4ECGX_REGSET(ISO_50, s5k4ecgx_ISO_100), /* map to 100 */ + S5K4ECGX_REGSET(ISO_100, s5k4ecgx_ISO_100), + S5K4ECGX_REGSET(ISO_200, s5k4ecgx_ISO_200), + S5K4ECGX_REGSET(ISO_400, s5k4ecgx_ISO_400), + S5K4ECGX_REGSET(ISO_800, s5k4ecgx_ISO_400), /* map to 400 */ + S5K4ECGX_REGSET(ISO_1600, s5k4ecgx_ISO_400), /* map to 400 */ + S5K4ECGX_REGSET(ISO_SPORTS, s5k4ecgx_ISO_Auto),/* map to auto */ + S5K4ECGX_REGSET(ISO_NIGHT, s5k4ecgx_ISO_Auto), /* map to auto */ + S5K4ECGX_REGSET(ISO_MOVIE, s5k4ecgx_ISO_Auto), /* map to auto */ + }, + .effect = { + S5K4ECGX_REGSET(IMAGE_EFFECT_NONE, s5k4ecgx_Effect_Normal), + S5K4ECGX_REGSET(IMAGE_EFFECT_BNW, s5k4ecgx_Effect_Black_White), + S5K4ECGX_REGSET(IMAGE_EFFECT_SEPIA, s5k4ecgx_Effect_Sepia), + S5K4ECGX_REGSET(IMAGE_EFFECT_NEGATIVE, + s5k4ecgx_Effect_Negative), + }, + .white_balance = { + S5K4ECGX_REGSET(WHITE_BALANCE_AUTO, s5k4ecgx_WB_Auto), + S5K4ECGX_REGSET(WHITE_BALANCE_SUNNY, s5k4ecgx_WB_Sunny), + S5K4ECGX_REGSET(WHITE_BALANCE_CLOUDY, s5k4ecgx_WB_Cloudy), + S5K4ECGX_REGSET(WHITE_BALANCE_TUNGSTEN, s5k4ecgx_WB_Tungsten), + S5K4ECGX_REGSET(WHITE_BALANCE_FLUORESCENT, + s5k4ecgx_WB_Fluorescent), + }, + .preview_size = { + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_QCIF, s5k4ecgx_176_Preview), + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_CIF, s5k4ecgx_352_Preview), + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_VGA, s5k4ecgx_640_Preview), + S5K4ECGX_REGSET(S5K4ECGX_PREVIEW_D1, s5k4ecgx_720_Preview), + }, + .capture_size = { + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_VGA, s5k4ecgx_VGA_Capture), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_1MP, s5k4ecgx_1M_Capture), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_2MP, s5k4ecgx_2M_Capture), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_3MP, s5k4ecgx_3M_Capture), + S5K4ECGX_REGSET(S5K4ECGX_CAPTURE_5MP, s5k4ecgx_5M_Capture), + }, + .scene_mode = { + S5K4ECGX_REGSET(SCENE_MODE_NONE, s5k4ecgx_Scene_Default), + S5K4ECGX_REGSET(SCENE_MODE_PORTRAIT, s5k4ecgx_Scene_Portrait), + S5K4ECGX_REGSET(SCENE_MODE_NIGHTSHOT, s5k4ecgx_Scene_Nightshot), + S5K4ECGX_REGSET(SCENE_MODE_LANDSCAPE, s5k4ecgx_Scene_Landscape), + S5K4ECGX_REGSET(SCENE_MODE_SPORTS, s5k4ecgx_Scene_Sports), + S5K4ECGX_REGSET(SCENE_MODE_PARTY_INDOOR, + s5k4ecgx_Scene_Party_Indoor), + S5K4ECGX_REGSET(SCENE_MODE_BEACH_SNOW, + s5k4ecgx_Scene_Beach_Snow), + S5K4ECGX_REGSET(SCENE_MODE_SUNSET, s5k4ecgx_Scene_Sunset), + S5K4ECGX_REGSET(SCENE_MODE_FIREWORKS, s5k4ecgx_Scene_Fireworks), + S5K4ECGX_REGSET(SCENE_MODE_CANDLE_LIGHT, + s5k4ecgx_Scene_Candle_Light), + }, + .saturation = { + S5K4ECGX_REGSET(SATURATION_MINUS_2, + s5k4ecgx_Saturation_Minus_2), + S5K4ECGX_REGSET(SATURATION_MINUS_1, + s5k4ecgx_Saturation_Minus_1), + S5K4ECGX_REGSET(SATURATION_DEFAULT, + s5k4ecgx_Saturation_Default), + S5K4ECGX_REGSET(SATURATION_PLUS_1, s5k4ecgx_Saturation_Plus_1), + S5K4ECGX_REGSET(SATURATION_PLUS_2, s5k4ecgx_Saturation_Plus_2), + }, + .contrast = { + S5K4ECGX_REGSET(CONTRAST_MINUS_2, s5k4ecgx_Contrast_Minus_2), + S5K4ECGX_REGSET(CONTRAST_MINUS_1, s5k4ecgx_Contrast_Minus_1), + S5K4ECGX_REGSET(CONTRAST_DEFAULT, s5k4ecgx_Contrast_Default), + S5K4ECGX_REGSET(CONTRAST_PLUS_1, s5k4ecgx_Contrast_Plus_1), + S5K4ECGX_REGSET(CONTRAST_PLUS_2, s5k4ecgx_Contrast_Plus_2), + }, + .sharpness = { + S5K4ECGX_REGSET(SHARPNESS_MINUS_2, s5k4ecgx_Sharpness_Minus_2), + S5K4ECGX_REGSET(SHARPNESS_MINUS_1, s5k4ecgx_Sharpness_Minus_1), + S5K4ECGX_REGSET(SHARPNESS_DEFAULT, s5k4ecgx_Sharpness_Default), + S5K4ECGX_REGSET(SHARPNESS_PLUS_1, s5k4ecgx_Sharpness_Plus_1), + S5K4ECGX_REGSET(SHARPNESS_PLUS_2, s5k4ecgx_Sharpness_Plus_2), + }, + .fps = { + S5K4ECGX_REGSET(FRAME_RATE_AUTO, s5k4ecgx_FPS_Auto), + S5K4ECGX_REGSET(FRAME_RATE_7, s5k4ecgx_FPS_7), + S5K4ECGX_REGSET(FRAME_RATE_15, s5k4ecgx_FPS_15), + S5K4ECGX_REGSET(FRAME_RATE_30, s5k4ecgx_FPS_30), + }, + .preview_return = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Preview_Return), + .jpeg_quality_high = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Jpeg_Quality_High), + .jpeg_quality_normal = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Jpeg_Quality_Normal), + .jpeg_quality_low = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Jpeg_Quality_Low), + .flash_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Flash_Start), + .flash_end = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Flash_End), + .af_assist_flash_start = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Pre_Flash_Start), + .af_assist_flash_end = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Pre_Flash_End), + .af_low_light_mode_on = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Low_Light_Mode_On), + .af_low_light_mode_off = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Low_Light_Mode_Off), + .ae_awb_lock_on = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AE_AWB_Lock_On), + .ae_awb_lock_off = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AE_AWB_Lock_Off), + .low_cap_on = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Low_Cap_On), + .low_cap_off = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Low_Cap_Off), + .wdr_on = S5K4ECGX_REGSET_TABLE(s5k4ecgx_WDR_on), + .wdr_off = S5K4ECGX_REGSET_TABLE(s5k4ecgx_WDR_off), + .face_detection_on = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Face_Detection_On), + .face_detection_off = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Face_Detection_Off), + .capture_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Capture_Start), + .af_macro_mode_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Macro_mode_1), + .af_macro_mode_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Macro_mode_2), + .af_macro_mode_3 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Macro_mode_3), + .af_normal_mode_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Normal_mode_1), + .af_normal_mode_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Normal_mode_2), + .af_normal_mode_3 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Normal_mode_3), + .af_return_macro_position = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_AF_Return_Macro_pos), + .single_af_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Single_AF_Start), + .single_af_off_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Single_AF_Off_1), + .single_af_off_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Single_AF_Off_2), + .dtp_start = S5K4ECGX_REGSET_TABLE(s5k4ecgx_DTP_init), + .dtp_stop = S5K4ECGX_REGSET_TABLE(s5k4ecgx_DTP_stop), + .init_reg_1 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_init_reg1), + .init_reg_2 = S5K4ECGX_REGSET_TABLE(s5k4ecgx_init_reg2), + .flash_init = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Flash_init), + .reset_crop = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Reset_Crop), + .get_ae_stable_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_Get_AE_Stable_Status), + .get_light_level = S5K4ECGX_REGSET_TABLE(s5k4ecgx_Get_Light_Level), + .get_1st_af_search_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_1st_af_search_status), + .get_2nd_af_search_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_2nd_af_search_status), + .get_capture_status = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_capture_status), + .get_esd_status = S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_esd_status), + .get_iso = S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_iso_reg), + .get_shutterspeed = + S5K4ECGX_REGSET_TABLE(s5k4ecgx_get_shutterspeed_reg), +}; +#endif + +struct s5k4ecgx_state { + struct s5k4ecgx_platform_data *pdata; + struct v4l2_subdev sd; + struct v4l2_pix_format pix; + struct v4l2_fract timeperframe; + struct s5k4ecgx_jpeg_param jpeg; + struct s5k4ecgx_version fw; + struct s5k4ecgx_version prm; + struct s5k4ecgx_date_info dateinfo; + struct s5k4ecgx_position position; + struct v4l2_streamparm strm; + struct s5k4ecgx_gps_info gps_info; + struct mutex ctrl_lock; + enum s5k4ecgx_runmode runmode; + enum s5k4ecgx_oprmode oprmode; + enum af_operation_status af_status; + int preview_framesize_index; + int capture_framesize_index; + int sensor_version; + int freq; /* MCLK in Hz */ + int check_dataline; + int check_previewdata; + bool flash_on; + bool torch_on; + bool sensor_af_in_low_light_mode; + bool flash_state_on_previous_capture; + bool initialized; + bool restore_preview_size_needed; + int one_frame_delay_ms; + const struct s5k4ecgx_regs *regs; +}; + +static const struct v4l2_mbus_framefmt capture_fmts[] = { + { + .code = V4L2_MBUS_FMT_FIXED, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; + +/** + * s5k4ecgx_i2c_read_twobyte: Read 2 bytes from sensor + */ +static int s5k4ecgx_i2c_read_twobyte(struct i2c_client *client, + u16 subaddr, u16 *data) +{ + int err; + unsigned char buf[2]; + struct i2c_msg msg[2]; + + cpu_to_be16s(&subaddr); + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = (u8 *)&subaddr; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = buf; + + err = i2c_transfer(client->adapter, msg, 2); + if (unlikely(err != 2)) { + dev_err(&client->dev, + "%s: register read fail\n", __func__); + return -EIO; + } + + *data = ((buf[0] << 8) | buf[1]); + + return 0; +} + +/** + * s5k4ecgx_i2c_write_twobyte: Write (I2C) multiple bytes to the camera sensor + * @client: pointer to i2c_client + * @cmd: command register + * @w_data: data to be written + * @w_len: length of data to be written + * + * Returns 0 on success, <0 on error + */ +static int s5k4ecgx_i2c_write_twobyte(struct i2c_client *client, + u16 addr, u16 w_data) +{ + int retry_count = 5; + unsigned char buf[4]; + struct i2c_msg msg = {client->addr, 0, 4, buf}; + int ret = 0; + + buf[0] = addr >> 8; + buf[1] = addr; + buf[2] = w_data >> 8; + buf[3] = w_data & 0xff; + + s5k4ecgx_debug(S5K4ECGX_DEBUG_I2C, "%s : W(0x%02X%02X%02X%02X)\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + + do { + ret = i2c_transfer(client->adapter, &msg, 1); + if (likely(ret == 1)) + break; + msleep(POLL_TIME_MS); + dev_err(&client->dev, "%s: I2C err %d, retry %d.\n", + __func__, ret, retry_count); + } while (retry_count-- > 0); + if (ret != 1) { + dev_err(&client->dev, "%s: I2C is not working.\n", __func__); + return -EIO; + } + + return 0; +} + +static int s5k4ecgx_write_regs(struct v4l2_subdev *sd, const u32 regs[], + int size) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i, err; + + for (i = 0; i < size; i++) { + err = s5k4ecgx_i2c_write_twobyte(client, + (regs[i] >> 16), regs[i]); + if (unlikely(err != 0)) { + dev_err(&client->dev, + "%s: register write failed\n", __func__); + return err; + } + } + + return 0; +} + +static int s5k4ecgx_set_from_table(struct v4l2_subdev *sd, + const char *setting_name, + const struct s5k4ecgx_regset_table *table, + int table_size, int index) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + dev_dbg(&client->dev, "%s: set %s index %d\n", + __func__, setting_name, index); + if ((index < 0) || (index >= table_size)) { + dev_err(&client->dev, + "%s: index(%d) out of range[0:%d] for table for %s\n", + __func__, index, table_size, setting_name); + return -EINVAL; + } + table += index; + if (table->reg == NULL) + return -EINVAL; + return s5k4ecgx_write_regs(sd, table->reg, table->array_size); +} + +static int s5k4ecgx_set_parameter(struct v4l2_subdev *sd, + int *current_value_ptr, + int new_value, + const char *setting_name, + const struct s5k4ecgx_regset_table *table, + int table_size) +{ + int err; + + if (*current_value_ptr == new_value) + return 0; + + err = s5k4ecgx_set_from_table(sd, setting_name, table, + table_size, new_value); + + if (!err) + *current_value_ptr = new_value; + return err; +} + +static int s5k4ecgx_set_preview_stop(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) + state->runmode = S5K4ECGX_RUNMODE_IDLE; + + dev_dbg(&client->dev, "%s:\n", __func__); + + return 0; +} + +static int s5k4ecgx_set_preview_start(struct v4l2_subdev *sd) +{ + int err; + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + bool set_size = true; + + dev_dbg(&client->dev, "%s: runmode = %d\n", + __func__, state->runmode); + + if (!state->pix.width || !state->pix.height || + !state->strm.parm.capture.timeperframe.denominator) + return -EINVAL; + + if (state->runmode == S5K4ECGX_RUNMODE_CAPTURE) { + dev_dbg(&client->dev, "%s: sending Preview_Return cmd\n", + __func__); + err = s5k4ecgx_set_from_table(sd, "preview return", + &state->regs->preview_return, 1, 0); + if (err < 0) { + dev_err(&client->dev, + "%s: failed: s5k4ecgx_Preview_Return\n", + __func__); + return -EIO; + } + set_size = state->restore_preview_size_needed; + } + + if (set_size) { + err = s5k4ecgx_set_from_table(sd, "preview_size", + state->regs->preview_size, + ARRAY_SIZE(state->regs->preview_size), + state->preview_framesize_index); + if (err < 0) { + dev_err(&client->dev, + "%s: failed: Could not set preview size\n", + __func__); + return -EIO; + } + } + + dev_dbg(&client->dev, "%s: runmode now RUNNING\n", __func__); + state->runmode = S5K4ECGX_RUNMODE_RUNNING; + + return 0; +} + +static int s5k4ecgx_set_capture_size(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + int err; + + dev_dbg(&client->dev, "%s: index:%d\n", __func__, + state->capture_framesize_index); + + err = s5k4ecgx_set_from_table(sd, "capture_size", + state->regs->capture_size, + ARRAY_SIZE(state->regs->capture_size), + state->capture_framesize_index); + if (err < 0) { + dev_err(&client->dev, + "%s: failed: i2c_write for capture_size index %d\n", + __func__, state->capture_framesize_index); + } + state->runmode = S5K4ECGX_RUNMODE_CAPTURE; + + return err; +} + +static int s5k4ecgx_set_jpeg_quality(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + dev_dbg(&client->dev, + "%s: jpeg.quality %d\n", __func__, state->jpeg.quality); + if (state->jpeg.quality < 0) + state->jpeg.quality = 0; + if (state->jpeg.quality > 100) + state->jpeg.quality = 100; + + switch (state->jpeg.quality) { + case 90 ... 100: + dev_dbg(&client->dev, + "%s: setting to high jpeg quality\n", __func__); + return s5k4ecgx_set_from_table(sd, "jpeg quality high", + &state->regs->jpeg_quality_high, 1, 0); + case 80 ... 89: + dev_dbg(&client->dev, + "%s: setting to normal jpeg quality\n", __func__); + return s5k4ecgx_set_from_table(sd, "jpeg quality normal", + &state->regs->jpeg_quality_normal, 1, 0); + default: + dev_dbg(&client->dev, + "%s: setting to low jpeg quality\n", __func__); + return s5k4ecgx_set_from_table(sd, "jpeg quality low", + &state->regs->jpeg_quality_low, 1, 0); + } +} + +static u16 s5k4ecgx_get_light_level(struct v4l2_subdev *sd) +{ + int err; + u16 read_value = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + err = s5k4ecgx_set_from_table(sd, "get light level", + &state->regs->get_light_level, 1, 0); + if (err) { + dev_err(&client->dev, + "%s: write cmd failed, returning 0\n", __func__); + goto out; + } + err = s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + if (err) { + dev_err(&client->dev, + "%s: read cmd failed, returning 0\n", __func__); + goto out; + } + + dev_dbg(&client->dev, "%s: read_value = %d (0x%X)\n", + __func__, read_value, read_value); + +out: + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + return read_value; +} + +static int s5k4ecgx_start_capture(struct v4l2_subdev *sd) +{ + int err; + u16 read_value; + u16 light_level; + int poll_time_ms; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + + /* reset cropping if our current preview is not 640x480, + * otherwise the capture will be wrong because of the cropping + */ + if (state->preview_framesize_index != S5K4ECGX_PREVIEW_VGA) { + int err = s5k4ecgx_set_from_table(sd, "reset crop", + &state->regs->reset_crop, 1, 0); + if (err < 0) { + dev_err(&client->dev, + "%s: failed: Could not set preview size\n", + __func__); + return -EIO; + } + state->restore_preview_size_needed = true; + } else + state->restore_preview_size_needed = false; + + msleep(50); + light_level = s5k4ecgx_get_light_level(sd); + + dev_dbg(&client->dev, "%s: light_level = %d\n", __func__, + light_level); + + state->flash_state_on_previous_capture = false; + if (parms->scene_mode != SCENE_MODE_NIGHTSHOT) { + switch (parms->flash_mode) { + case FLASH_MODE_AUTO: + if (light_level > LOW_LIGHT_LEVEL) { + /* light level bright enough + * that we don't need flash + */ + break; + } + /* fall through to flash start */ + case FLASH_MODE_ON: + if (parms->focus_mode == FOCUS_MODE_INFINITY) { + s5k4ecgx_set_from_table(sd, + "AF assist flash start", + &state->regs->af_assist_flash_start, + 1, 0); + s5k4ecgx_set_from_table(sd, + "AF assist flash end", + &state->regs->af_assist_flash_end, + 1, 0); + msleep(10); + } + s5k4ecgx_set_from_table(sd, "flash start", + &state->regs->flash_start, 1, 0); + state->flash_on = true; + state->flash_state_on_previous_capture = true; + pdata->flash_onoff(1); + break; + default: + break; + } + } + + /* if light is low, use low light capture settings, EXCEPT + * if scene mode set to NIGHTSHOT or SPORTS because they + * have their own settings (though a low light sport setting + * could be useful) + */ + if ((light_level <= HIGH_LIGHT_LEVEL) && + (parms->scene_mode != SCENE_MODE_NIGHTSHOT) && + (parms->scene_mode != SCENE_MODE_SPORTS)) { + s5k4ecgx_set_from_table(sd, "low cap on", + &state->regs->low_cap_on, 1, 0); + } + + err = s5k4ecgx_set_capture_size(sd); + if (err < 0) { + dev_err(&client->dev, + "%s: failed: i2c_write for capture_resolution\n", + __func__); + return -EIO; + } + + dev_dbg(&client->dev, "%s: send Capture_Start cmd\n", __func__); + s5k4ecgx_set_from_table(sd, "capture start", + &state->regs->capture_start, 1, 0); + + /* a shot takes takes at least 50ms so sleep that amount first + * and then start polling for completion. + */ + msleep(50); + /* Enter read mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x002C, 0x7000); + poll_time_ms = 50; + do { + s5k4ecgx_set_from_table(sd, "get capture status", + &state->regs->get_capture_status, 1, 0); + s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + dev_dbg(&client->dev, + "%s: s5k4ecgx_Capture_Start check = %#x\n", + __func__, read_value); + if (read_value != 0x00) + break; + msleep(POLL_TIME_MS); + poll_time_ms += POLL_TIME_MS; + } while (poll_time_ms < CAPTURE_POLL_TIME_MS); + + dev_dbg(&client->dev, "%s: capture done check finished after %d ms\n", + __func__, poll_time_ms); + + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + s5k4ecgx_set_from_table(sd, "ae awb lock off", + &state->regs->ae_awb_lock_off, 1, 0); + + if ((light_level <= HIGH_LIGHT_LEVEL) && + (parms->scene_mode != SCENE_MODE_NIGHTSHOT) && + (parms->scene_mode != SCENE_MODE_SPORTS)) { + s5k4ecgx_set_from_table(sd, "low cap off", + &state->regs->low_cap_off, 1, 0); + } + + if ((parms->scene_mode != SCENE_MODE_NIGHTSHOT) && (state->flash_on)) { + state->flash_on = false; + pdata->flash_onoff(0); + s5k4ecgx_set_from_table(sd, "flash end", + &state->regs->flash_end, 1, 0); + } + + return 0; +} + +/* wide dynamic range support */ +static int s5k4ecgx_set_wdr(struct v4l2_subdev *sd, int value) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + if (value == WDR_ON) + return s5k4ecgx_set_from_table(sd, "wdr on", + &state->regs->wdr_on, 1, 0); + return s5k4ecgx_set_from_table(sd, "wdr off", + &state->regs->wdr_off, 1, 0); +} + +static int s5k4ecgx_set_face_detection(struct v4l2_subdev *sd, int value) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + if (value == FACE_DETECTION_ON) + return s5k4ecgx_set_from_table(sd, "face detection on", + &state->regs->face_detection_on, 1, 0); + return s5k4ecgx_set_from_table(sd, "face detection off", + &state->regs->face_detection_off, 1, 0); +} + +static int s5k4ecgx_return_focus(struct v4l2_subdev *sd) +{ + int err; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + err = s5k4ecgx_set_from_table(sd, + "af normal mode 1", + &state->regs->af_normal_mode_1, 1, 0); + if (err < 0) + goto fail; + msleep(FIRST_SETTING_FOCUS_MODE_DELAY_MS); + err = s5k4ecgx_set_from_table(sd, + "af normal mode 2", + &state->regs->af_normal_mode_2, 1, 0); + if (err < 0) + goto fail; + msleep(SECOND_SETTING_FOCUS_MODE_DELAY_MS); + err = s5k4ecgx_set_from_table(sd, + "af normal mode 3", + &state->regs->af_normal_mode_3, 1, 0); + if (err < 0) + goto fail; + + return 0; +fail: + dev_err(&client->dev, + "%s: i2c_write failed\n", __func__); + return -EIO; +} + +static int s5k4ecgx_set_focus_mode(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + int err; + + if (parms->focus_mode == value) + return 0; + + dev_dbg(&client->dev, "%s value(%d)\n", __func__, value); + + switch (value) { + case FOCUS_MODE_MACRO: + dev_dbg(&client->dev, + "%s: FOCUS_MODE_MACRO\n", __func__); + err = s5k4ecgx_set_from_table(sd, "af macro mode 1", + &state->regs->af_macro_mode_1, 1, 0); + if (err < 0) + goto fail; + msleep(FIRST_SETTING_FOCUS_MODE_DELAY_MS); + err = s5k4ecgx_set_from_table(sd, "af macro mode 2", + &state->regs->af_macro_mode_2, 1, 0); + if (err < 0) + goto fail; + msleep(SECOND_SETTING_FOCUS_MODE_DELAY_MS); + err = s5k4ecgx_set_from_table(sd, "af macro mode 3", + &state->regs->af_macro_mode_3, 1, 0); + if (err < 0) + goto fail; + parms->focus_mode = FOCUS_MODE_MACRO; + break; + + case FOCUS_MODE_INFINITY: + case FOCUS_MODE_AUTO: + err = s5k4ecgx_set_from_table(sd, + "af normal mode 1", + &state->regs->af_normal_mode_1, 1, 0); + if (err < 0) + goto fail; + msleep(FIRST_SETTING_FOCUS_MODE_DELAY_MS); + err = s5k4ecgx_set_from_table(sd, + "af normal mode 2", + &state->regs->af_normal_mode_2, 1, 0); + if (err < 0) + goto fail; + msleep(SECOND_SETTING_FOCUS_MODE_DELAY_MS); + err = s5k4ecgx_set_from_table(sd, + "af normal mode 3", + &state->regs->af_normal_mode_3, 1, 0); + if (err < 0) + goto fail; + parms->focus_mode = value; + break; + default: + return -EINVAL; + break; + } + + return 0; +fail: + dev_err(&client->dev, + "%s: i2c_write failed\n", __func__); + return -EIO; +} + +static void s5k4ecgx_auto_focus_flash_start(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + int count; + u16 read_value; + + s5k4ecgx_set_from_table(sd, "AF assist flash start", + &state->regs->af_assist_flash_start, 1, 0); + state->flash_on = true; + pdata->af_assist_onoff(1); + + /* delay 200ms (SLSI value) and then poll to see if AE is stable. + * once it is stable, lock it and then return to do AF + */ + msleep(200); + + /* enter read mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x002C, 0x7000); + for (count = 0; count < AE_STABLE_SEARCH_COUNT; count++) { + if (state->af_status == AF_CANCEL) + break; + s5k4ecgx_set_from_table(sd, "get ae stable status", + &state->regs->get_ae_stable_status, 1, 0); + s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + dev_dbg(&client->dev, "%s: ae stable status = %#x\n", + __func__, read_value); + if (read_value == 0x1) + break; + msleep(state->one_frame_delay_ms); + } + + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + /* if we were cancelled, turn off flash */ + if (state->af_status == AF_CANCEL) { + dev_dbg(&client->dev, + "%s: AF cancelled\n", __func__); + s5k4ecgx_set_from_table(sd, "AF assist flash end", + &state->regs->af_assist_flash_end, 1, 0); + state->flash_on = false; + pdata->af_assist_onoff(0); + } +} + +static int s5k4ecgx_start_auto_focus(struct v4l2_subdev *sd) +{ + int light_level; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + + dev_dbg(&client->dev, "%s: start SINGLE AF operation, flash mode %d\n", + __func__, parms->flash_mode); + + /* in case user calls auto_focus repeatedly without a cancel + * or a capture, we need to cancel here to allow ae_awb + * to work again, or else we could be locked forever while + * that app is running, which is not the expected behavior. + */ + s5k4ecgx_set_from_table(sd, "ae awb lock off", + &state->regs->ae_awb_lock_off, 1, 0); + + if (parms->scene_mode == SCENE_MODE_NIGHTSHOT) { + /* user selected night shot mode, assume we need low light + * af mode. flash is always off in night shot mode + */ + goto enable_af_low_light_mode; + } + + light_level = s5k4ecgx_get_light_level(sd); + + switch (parms->flash_mode) { + case FLASH_MODE_AUTO: + if (light_level > LOW_LIGHT_LEVEL) { + /* flash not needed */ + break; + } + /* fall through to turn on flash for AF assist */ + case FLASH_MODE_ON: + s5k4ecgx_auto_focus_flash_start(sd); + if (state->af_status == AF_CANCEL) + return 0; + break; + case FLASH_MODE_OFF: + break; + default: + dev_err(&client->dev, + "%s: Unknown Flash mode 0x%x\n", + __func__, parms->flash_mode); + break; + } + + if (light_level > LOW_LIGHT_LEVEL) { + if (state->sensor_af_in_low_light_mode) { + state->sensor_af_in_low_light_mode = false; + s5k4ecgx_set_from_table(sd, "af low light mode off", + &state->regs->af_low_light_mode_off, 1, 0); + } + } else { +enable_af_low_light_mode: + if (!state->sensor_af_in_low_light_mode) { + state->sensor_af_in_low_light_mode = true; + s5k4ecgx_set_from_table(sd, "af low light mode on", + &state->regs->af_low_light_mode_on, 1, 0); + } + } + + s5k4ecgx_set_from_table(sd, "single af start", + &state->regs->single_af_start, 1, 0); + state->af_status = AF_INITIAL; + dev_dbg(&client->dev, "%s: af_status set to start\n", __func__); + + return 0; +} + +/* called by HAL after auto focus was finished. + * it might off the assist flash + */ +static int s5k4ecgx_finish_auto_focus(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + if (state->flash_on) { + struct s5k4ecgx_platform_data *pd = client->dev.platform_data; + s5k4ecgx_set_from_table(sd, "AF assist flash end", + &state->regs->af_assist_flash_end, 1, 0); + state->flash_on = false; + pd->af_assist_onoff(0); + } + + dev_dbg(&client->dev, "%s: single AF finished\n", __func__); + state->af_status = AF_NONE; + return 0; +} + +static int s5k4ecgx_stop_auto_focus(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + int focus_mode = parms->focus_mode; + + dev_dbg(&client->dev, "%s: single AF Off command Setting\n", __func__); + + /* always cancel ae_awb, in case AF already finished before + * we got called. + */ + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + s5k4ecgx_set_from_table(sd, "ae awb lock off", + &state->regs->ae_awb_lock_off, 1, 0); + if (state->flash_on) + s5k4ecgx_finish_auto_focus(sd); + + if (state->af_status != AF_START) { + /* we weren't in the middle auto focus operation, we're done */ + dev_dbg(&client->dev, + "%s: auto focus not in progress, done\n", __func__); + + if (focus_mode == FOCUS_MODE_MACRO) { + /* for change focus mode forcely */ + parms->focus_mode = -1; + s5k4ecgx_set_focus_mode(sd, FOCUS_MODE_MACRO); + } else if (focus_mode == FOCUS_MODE_AUTO) { + /* for change focus mode forcely */ + parms->focus_mode = -1; + s5k4ecgx_set_focus_mode(sd, FOCUS_MODE_AUTO); + } + + return 0; + } + + /* auto focus was in progress. the other thread + * is either in the middle of s5k4ecgx_get_auto_focus_result_first(), + * s5k4ecgx_get_auto_focus_result_second() + * or will call it shortly. set a flag to have + * it abort it's polling. that thread will + * also do cleanup like restore focus position. + * + * it might be enough to just send sensor commands + * to abort auto focus and the other thread would get + * that state from it's polling calls, but I'm not sure. + */ + state->af_status = AF_CANCEL; + dev_dbg(&client->dev, + "%s: sending Single_AF_Off commands to sensor\n", __func__); + + s5k4ecgx_set_from_table(sd, "single af off 1", + &state->regs->single_af_off_1, 1, 0); + + msleep(state->one_frame_delay_ms); + + s5k4ecgx_set_from_table(sd, "single af off 2", + &state->regs->single_af_off_2, 1, 0); + + return 0; +} + +/* called by HAL after auto focus was started to get the first search result*/ +static int s5k4ecgx_get_auto_focus_result_first(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + u16 read_value; + + if (state->af_status == AF_INITIAL) { + dev_dbg(&client->dev, "%s: Check AF Result\n", __func__); + if (state->af_status == AF_NONE) { + dev_dbg(&client->dev, + "%s: auto focus never started, returning 0x2\n", + __func__); + ctrl->value = AUTO_FOCUS_CANCELLED; + return 0; + } + + /* must delay 2 frame times before checking result of 1st phase */ + mutex_unlock(&state->ctrl_lock); + msleep(state->one_frame_delay_ms*2); + mutex_lock(&state->ctrl_lock); + + /* lock AE and AWB after first AF search */ + s5k4ecgx_set_from_table(sd, "ae awb lock on", + &state->regs->ae_awb_lock_on, 1, 0); + + dev_dbg(&client->dev, "%s: 1st AF search\n", __func__); + /* enter read mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x002C, 0x7000); + state->af_status = AF_START; + } else if (state->af_status == AF_CANCEL) { + dev_dbg(&client->dev, + "%s: AF is cancelled while doing\n", __func__); + ctrl->value = AUTO_FOCUS_CANCELLED; + s5k4ecgx_finish_auto_focus(sd); + return 0; + } + s5k4ecgx_set_from_table(sd, "get 1st af search status", + &state->regs->get_1st_af_search_status, + 1, 0); + s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + dev_dbg(&client->dev, + "%s: 1st i2c_read --- read_value == 0x%x\n", + __func__, read_value); + ctrl->value = read_value; + return 0; +} + +/* called by HAL after first search was succeed to get the second search result*/ +static int s5k4ecgx_get_auto_focus_result_second(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + u16 read_value; + + if (state->af_status == AF_CANCEL) { + dev_dbg(&client->dev, + "%s: AF is cancelled while doing\n", __func__); + ctrl->value = AUTO_FOCUS_CANCELLED; + s5k4ecgx_finish_auto_focus(sd); + return 0; + } + s5k4ecgx_set_from_table(sd, "get 2nd af search status", + &state->regs->get_2nd_af_search_status, + 1, 0); + s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + dev_dbg(&client->dev, + "%s: 2nd i2c_read --- read_value == 0x%x\n", + __func__, read_value); + ctrl->value = read_value; + return 0; +} + +static void s5k4ecgx_init_parameters(struct v4l2_subdev *sd) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + + state->strm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + parms->capture.capturemode = 0; + parms->capture.timeperframe.numerator = 1; + parms->capture.timeperframe.denominator = 30; + parms->contrast = CONTRAST_DEFAULT; + parms->effects = IMAGE_EFFECT_NONE; + parms->brightness = EV_DEFAULT; + parms->flash_mode = FLASH_MODE_AUTO; + parms->focus_mode = FOCUS_MODE_AUTO; + parms->iso = ISO_AUTO; + parms->metering = METERING_CENTER; + parms->saturation = SATURATION_DEFAULT; + parms->scene_mode = SCENE_MODE_NONE; + parms->sharpness = SHARPNESS_DEFAULT; + parms->white_balance = WHITE_BALANCE_AUTO; + + state->jpeg.enable = 0; + state->jpeg.quality = 100; + state->jpeg.main_offset = 0; + state->jpeg.main_size = 0; + state->jpeg.thumb_offset = 0; + state->jpeg.thumb_size = 0; + state->jpeg.postview_offset = 0; + + state->fw.major = 1; + + state->one_frame_delay_ms = NORMAL_MODE_MAX_ONE_FRAME_DELAY_MS; +} + +static void s5k4ecgx_set_framesize(struct v4l2_subdev *sd, + const struct s5k4ecgx_framesize *frmsize, + int frmsize_count, bool exact_match); +static int s5k4ecgx_s_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + dev_dbg(&client->dev, "%s: code = 0x%x, field = 0x%x," + " colorspace = 0x%x, width = %d, height = %d\n", + __func__, fmt->code, fmt->field, + fmt->colorspace, + fmt->width, fmt->height); + + if (fmt->code == V4L2_MBUS_FMT_FIXED && + fmt->colorspace != V4L2_COLORSPACE_JPEG) { + dev_err(&client->dev, + "%s: mismatch in pixelformat and colorspace\n", + __func__); + return -EINVAL; + } + + state->pix.width = fmt->width; + state->pix.height = fmt->height; + if (fmt->colorspace == V4L2_COLORSPACE_JPEG) + state->pix.pixelformat = V4L2_PIX_FMT_JPEG; + else + state->pix.pixelformat = 0; /* is this used anywhere? */ + + if (fmt->colorspace == V4L2_COLORSPACE_JPEG) { + state->oprmode = S5K4ECGX_OPRMODE_IMAGE; + /* + * In case of image capture mode, + * if the given image resolution is not supported, + * use the next higher image resolution. */ + s5k4ecgx_set_framesize(sd, s5k4ecgx_capture_framesize_list, + ARRAY_SIZE(s5k4ecgx_capture_framesize_list), + false); + + } else { + state->oprmode = S5K4ECGX_OPRMODE_VIDEO; + /* + * In case of video mode, + * if the given video resolution is not matching, use + * the default rate (currently S5K4ECGX_PREVIEW_WVGA). + */ + s5k4ecgx_set_framesize(sd, s5k4ecgx_preview_framesize_list, + ARRAY_SIZE(s5k4ecgx_preview_framesize_list), + true); + } + + state->jpeg.enable = state->pix.pixelformat == V4L2_PIX_FMT_JPEG; + + return 0; +} + +static int s5k4ecgx_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_frmsizeenum *fsize) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + /* The camera interface should read this value, this is the resolution + * at which the sensor would provide framedata to the camera i/f + * + * In case of image capture, + * this returns the default camera resolution (SVGA) + */ + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = state->pix.width; + fsize->discrete.height = state->pix.height; + return 0; +} + +static int s5k4ecgx_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + pr_debug("%s: index = %d\n", __func__, index); + + if (index >= ARRAY_SIZE(capture_fmts)) + return -EINVAL; + + *code = capture_fmts[index].code; + + return 0; +} + +static int s5k4ecgx_try_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) +{ + int num_entries; + int i; + + num_entries = ARRAY_SIZE(capture_fmts); + + pr_debug("%s: code = 0x%x, colorspace = 0x%x, num_entries = %d\n", + __func__, fmt->code, fmt->colorspace, num_entries); + + for (i = 0; i < num_entries; i++) { + if (capture_fmts[i].code == fmt->code && + capture_fmts[i].colorspace == fmt->colorspace) { + pr_debug("%s: match found, returning 0\n", __func__); + return 0; + } + } + + pr_debug("%s: no match found, returning -EINVAL\n", __func__); + return -EINVAL; +} + +static void s5k4ecgx_enable_torch(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + + s5k4ecgx_set_from_table(sd, "torch start", + &state->regs->flash_start, 1, 0); + state->torch_on = true; + pdata->torch_onoff(1); +} + +static void s5k4ecgx_disable_torch(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + + if (state->torch_on) { + state->torch_on = false; + pdata->torch_onoff(0); + s5k4ecgx_set_from_table(sd, "torch end", + &state->regs->flash_end, 1, 0); + } +} +static int s5k4ecgx_set_flash_mode(struct v4l2_subdev *sd, int value) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + + if (parms->flash_mode == value) + return 0; + + if ((value >= FLASH_MODE_OFF) && (value <= FLASH_MODE_TORCH)) { + pr_debug("%s: setting flash mode to %d\n", + __func__, value); + parms->flash_mode = value; + if (parms->flash_mode == FLASH_MODE_TORCH) + s5k4ecgx_enable_torch(sd); + else + s5k4ecgx_disable_torch(sd); + return 0; + } + pr_debug("%s: trying to set invalid flash mode %d\n", + __func__, value); + return -EINVAL; +} + +static int s5k4ecgx_g_parm(struct v4l2_subdev *sd, + struct v4l2_streamparm *param) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + memcpy(param, &state->strm, sizeof(param)); + return 0; +} + +static int s5k4ecgx_s_parm(struct v4l2_subdev *sd, + struct v4l2_streamparm *param) +{ + int err = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *new_parms = + (struct sec_cam_parm *)¶m->parm.raw_data; + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + + dev_dbg(&client->dev, "%s: start\n", __func__); + + if (param->parm.capture.timeperframe.numerator != + parms->capture.timeperframe.numerator || + param->parm.capture.timeperframe.denominator != + parms->capture.timeperframe.denominator) { + + int fps = 0; + int fps_max = 30; + + if (param->parm.capture.timeperframe.numerator && + param->parm.capture.timeperframe.denominator) + fps = + (int)(param->parm.capture.timeperframe.denominator / + param->parm.capture.timeperframe.numerator); + else + fps = 0; + + if (fps <= 0 || fps > fps_max) { + dev_err(&client->dev, + "%s: Framerate %d not supported," + " setting it to %d fps.\n", + __func__, fps, fps_max); + fps = fps_max; + } + + /* + * Don't set the fps value, just update it in the state + * We will set the resolution and + * fps in the start operation (preview/capture) call + */ + parms->capture.timeperframe.numerator = 1; + parms->capture.timeperframe.denominator = fps; + } + + /* we return an error if one happened but don't stop trying to + * set all parameters passed + */ + err = s5k4ecgx_set_parameter(sd, &parms->contrast, new_parms->contrast, + "contrast", state->regs->contrast, + ARRAY_SIZE(state->regs->contrast)); + err |= s5k4ecgx_set_parameter(sd, &parms->effects, new_parms->effects, + "effect", state->regs->effect, + ARRAY_SIZE(state->regs->effect)); + err |= s5k4ecgx_set_parameter(sd, &parms->brightness, + new_parms->brightness, "brightness", + state->regs->ev, ARRAY_SIZE(state->regs->ev)); + err |= s5k4ecgx_set_flash_mode(sd, new_parms->flash_mode); + /* Must delay 150ms before setting macro mode due to a camera + * sensor requirement */ + if ((new_parms->focus_mode == FOCUS_MODE_MACRO) && + (parms->focus_mode != FOCUS_MODE_MACRO)) + msleep(150); + err |= s5k4ecgx_set_focus_mode(sd, new_parms->focus_mode); + err |= s5k4ecgx_set_parameter(sd, &parms->iso, new_parms->iso, + "iso", state->regs->iso, + ARRAY_SIZE(state->regs->iso)); + err |= s5k4ecgx_set_parameter(sd, &parms->metering, new_parms->metering, + "metering", state->regs->metering, + ARRAY_SIZE(state->regs->metering)); + err |= s5k4ecgx_set_parameter(sd, &parms->saturation, + new_parms->saturation, "saturation", + state->regs->saturation, + ARRAY_SIZE(state->regs->saturation)); + err |= s5k4ecgx_set_parameter(sd, &parms->scene_mode, + new_parms->scene_mode, "scene_mode", + state->regs->scene_mode, + ARRAY_SIZE(state->regs->scene_mode)); + err |= s5k4ecgx_set_parameter(sd, &parms->sharpness, + new_parms->sharpness, "sharpness", + state->regs->sharpness, + ARRAY_SIZE(state->regs->sharpness)); + err |= s5k4ecgx_set_parameter(sd, &parms->white_balance, + new_parms->white_balance, "white balance", + state->regs->white_balance, + ARRAY_SIZE(state->regs->white_balance)); + err |= s5k4ecgx_set_parameter(sd, &parms->fps, + new_parms->fps, "fps", + state->regs->fps, + ARRAY_SIZE(state->regs->fps)); + + if (parms->scene_mode == SCENE_MODE_NIGHTSHOT) + state->one_frame_delay_ms = NIGHT_MODE_MAX_ONE_FRAME_DELAY_MS; + else + state->one_frame_delay_ms = NORMAL_MODE_MAX_ONE_FRAME_DELAY_MS; + + dev_dbg(&client->dev, "%s: returning %d\n", __func__, err); + return err; +} + +/* This function is called from the g_ctrl api + * + * This function should be called only after the s_fmt call, + * which sets the required width/height value. + * + * It checks a list of available frame sizes and sets the + * most appropriate frame size. + * + * The list is stored in an increasing order (as far as possible). + * Hence the first entry (searching from the beginning) where both the + * width and height is more than the required value is returned. + * In case of no perfect match, we set the last entry (which is supposed + * to be the largest resolution supported.) + */ +static void s5k4ecgx_set_framesize(struct v4l2_subdev *sd, + const struct s5k4ecgx_framesize *frmsize, + int frmsize_count, bool preview) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct s5k4ecgx_framesize *last_frmsize = + &frmsize[frmsize_count - 1]; + + dev_dbg(&client->dev, "%s: Requested Res: %dx%d\n", __func__, + state->pix.width, state->pix.height); + + do { + /* + * In case of image capture mode, + * if the given image resolution is not supported, + * return the next higher image resolution. */ + if (preview) { + if (frmsize->width == state->pix.width && + frmsize->height == state->pix.height) { + break; + } + } else { + dev_dbg(&client->dev, + "%s: compare frmsize %dx%d to %dx%d\n", + __func__, + frmsize->width, frmsize->height, + state->pix.width, state->pix.height); + if (frmsize->width >= state->pix.width && + frmsize->height >= state->pix.height) { + dev_dbg(&client->dev, + "%s: select frmsize %dx%d, index=%d\n", + __func__, + frmsize->width, frmsize->height, + frmsize->index); + break; + } + } + + frmsize++; + } while (frmsize <= last_frmsize); + + if (frmsize > last_frmsize) + frmsize = last_frmsize; + + state->pix.width = frmsize->width; + state->pix.height = frmsize->height; + if (preview) { + state->preview_framesize_index = frmsize->index; + dev_dbg(&client->dev, "%s: Preview Res Set: %dx%d, index %d\n", + __func__, state->pix.width, state->pix.height, + state->preview_framesize_index); + } else { + state->capture_framesize_index = frmsize->index; + dev_dbg(&client->dev, "%s: Capture Res Set: %dx%d, index %d\n", + __func__, state->pix.width, state->pix.height, + state->capture_framesize_index); + } +} + +static int s5k4ecgx_check_dataline_stop(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + int err; + + dev_dbg(&client->dev, "%s\n", __func__); + + err = s5k4ecgx_set_from_table(sd, "DTP stop", + &state->regs->dtp_stop, 1, 0); + if (err < 0) { + v4l_info(client, "%s: register set failed\n", __func__); + return -EIO; + } + + state->check_dataline = 0; + + return err; +} + +static void s5k4ecgx_get_esd_int(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 read_value; + int err; + + if ((S5K4ECGX_RUNMODE_RUNNING == state->runmode) && + (state->af_status != AF_START)) { + err = s5k4ecgx_set_from_table(sd, "get esd status", + &state->regs->get_esd_status, + 1, 0); + err |= s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + dev_dbg(&client->dev, + "%s: read_value == 0x%x\n", __func__, read_value); + /* return to write mode */ + err |= s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + if (err < 0) { + v4l_info(client, + "Failed I2C for getting ESD information\n"); + ctrl->value = 0x01; + } else { + if (read_value != 0x0000) { + v4l_info(client, "ESD interrupt happened!!\n"); + ctrl->value = 0x01; + } else { + dev_dbg(&client->dev, + "%s: No ESD interrupt!!\n", __func__); + ctrl->value = 0x00; + } + } + } else + ctrl->value = 0x00; +} + +/* returns the real iso currently used by sensor due to lighting + * conditions, not the requested iso we sent using s_ctrl. + */ +static int s5k4ecgx_get_iso(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + int err; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + u16 read_value1 = 0; + u16 read_value2 = 0; + int read_value; + + err = s5k4ecgx_set_from_table(sd, "get iso", + &state->regs->get_iso, 1, 0); + err |= s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value1); + err |= s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value2); + + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + read_value = read_value1 * read_value2 / 384; + + if (read_value > 0x400) + ctrl->value = ISO_400; + else if (read_value > 0x200) + ctrl->value = ISO_200; + else if (read_value > 0x100) + ctrl->value = ISO_100; + else + ctrl->value = ISO_50; + + dev_dbg(&client->dev, "%s: get iso == %d (0x%x, 0x%x)\n", + __func__, ctrl->value, read_value1, read_value2); + + return err; +} + +static int s5k4ecgx_get_shutterspeed(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + int err; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + u16 read_value_1; + u16 read_value_2; + u32 read_value; + + err = s5k4ecgx_set_from_table(sd, "get shutterspeed", + &state->regs->get_shutterspeed, 1, 0); + err |= s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value_1); + err |= s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value_2); + + read_value = (read_value_2 << 16) | (read_value_1 & 0xffff); + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + + ctrl->value = read_value * 1000 / 400; + dev_dbg(&client->dev, + "%s: get shutterspeed == %d\n", __func__, ctrl->value); + + return err; +} + +static int s5k4ecgx_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + int err = 0; + + if (!state->initialized) { + dev_err(&client->dev, + "%s: return error because uninitialized\n", __func__); + return -ENODEV; + } + + mutex_lock(&state->ctrl_lock); + + switch (ctrl->id) { + case V4L2_CID_CAMERA_WHITE_BALANCE: + ctrl->value = parms->white_balance; + break; + case V4L2_CID_CAMERA_EFFECT: + ctrl->value = parms->effects; + break; + case V4L2_CID_CAMERA_CONTRAST: + ctrl->value = parms->contrast; + break; + case V4L2_CID_CAMERA_SATURATION: + ctrl->value = parms->saturation; + break; + case V4L2_CID_CAMERA_SHARPNESS: + ctrl->value = parms->sharpness; + break; + case V4L2_CID_CAM_JPEG_MAIN_SIZE: + ctrl->value = state->jpeg.main_size; + break; + case V4L2_CID_CAM_JPEG_MAIN_OFFSET: + ctrl->value = state->jpeg.main_offset; + break; + case V4L2_CID_CAM_JPEG_THUMB_SIZE: + ctrl->value = state->jpeg.thumb_size; + break; + case V4L2_CID_CAM_JPEG_THUMB_OFFSET: + ctrl->value = state->jpeg.thumb_offset; + break; + case V4L2_CID_CAM_JPEG_POSTVIEW_OFFSET: + ctrl->value = state->jpeg.postview_offset; + break; + case V4L2_CID_CAM_JPEG_MEMSIZE: + ctrl->value = SENSOR_JPEG_SNAPSHOT_MEMSIZE; + break; + case V4L2_CID_CAM_JPEG_QUALITY: + ctrl->value = state->jpeg.quality; + break; + case V4L2_CID_CAMERA_AUTO_FOCUS_RESULT_FIRST: + err = s5k4ecgx_get_auto_focus_result_first(sd, ctrl); + break; + case V4L2_CID_CAMERA_AUTO_FOCUS_RESULT_SECOND: + err = s5k4ecgx_get_auto_focus_result_second(sd, ctrl); + break; + case V4L2_CID_CAM_DATE_INFO_YEAR: + ctrl->value = 2010; + break; + case V4L2_CID_CAM_DATE_INFO_MONTH: + ctrl->value = 2; + break; + case V4L2_CID_CAM_DATE_INFO_DATE: + ctrl->value = 25; + break; + case V4L2_CID_CAM_SENSOR_VER: + ctrl->value = 1; + break; + case V4L2_CID_CAM_FW_MINOR_VER: + ctrl->value = state->fw.minor; + break; + case V4L2_CID_CAM_FW_MAJOR_VER: + ctrl->value = state->fw.major; + break; + case V4L2_CID_CAM_PRM_MINOR_VER: + ctrl->value = state->prm.minor; + break; + case V4L2_CID_CAM_PRM_MAJOR_VER: + ctrl->value = state->prm.major; + break; + case V4L2_CID_ESD_INT: + s5k4ecgx_get_esd_int(sd, ctrl); + break; + case V4L2_CID_CAMERA_GET_ISO: + err = s5k4ecgx_get_iso(sd, ctrl); + break; + case V4L2_CID_CAMERA_GET_SHT_TIME: + err = s5k4ecgx_get_shutterspeed(sd, ctrl); + break; + case V4L2_CID_CAMERA_GET_FLASH_ONOFF: + ctrl->value = state->flash_state_on_previous_capture; + break; + case V4L2_CID_CAMERA_OBJ_TRACKING_STATUS: + case V4L2_CID_CAMERA_SMART_AUTO_STATUS: + break; + default: + err = -ENOIOCTLCMD; + dev_err(&client->dev, "%s: unknown ctrl id 0x%x\n", + __func__, ctrl->id); + break; + } + + mutex_unlock(&state->ctrl_lock); + + return err; +} + +static int s5k4ecgx_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct sec_cam_parm *parms = + (struct sec_cam_parm *)&state->strm.parm.raw_data; + int err = 0; + int value = ctrl->value; + + if (!state->initialized && + (ctrl->id != V4L2_CID_CAMERA_CHECK_DATALINE)) { + dev_err(&client->dev, + "%s: return error because uninitialized\n", __func__); + return -ENODEV; + } + + dev_dbg(&client->dev, "%s: V4l2 control ID =%d, val = %d\n", + __func__, ctrl->id - V4L2_CID_PRIVATE_BASE, value); + + mutex_lock(&state->ctrl_lock); + + switch (ctrl->id) { + case V4L2_CID_CAMERA_FLASH_MODE: + err = s5k4ecgx_set_flash_mode(sd, value); + break; + case V4L2_CID_CAMERA_BRIGHTNESS: + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) { + err = s5k4ecgx_set_parameter(sd, &parms->brightness, + value, "brightness", + state->regs->ev, + ARRAY_SIZE(state->regs->ev)); + } else { + dev_err(&client->dev, + "%s: trying to set brightness when not " + "in preview mode\n", + __func__); + err = -EINVAL; + } + break; + case V4L2_CID_CAMERA_WHITE_BALANCE: + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) { + err = s5k4ecgx_set_parameter(sd, &parms->white_balance, + value, "white balance", + state->regs->white_balance, + ARRAY_SIZE(state->regs->white_balance)); + } else { + dev_err(&client->dev, + "%s: trying to set white balance when not " + "in preview mode\n", + __func__); + err = -EINVAL; + } + break; + case V4L2_CID_CAMERA_EFFECT: + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) { + err = s5k4ecgx_set_parameter(sd, &parms->effects, + value, "effects", state->regs->effect, + ARRAY_SIZE(state->regs->effect)); + } else { + dev_err(&client->dev, + "%s: trying to set effect when not " + "in preview mode\n", + __func__); + err = -EINVAL; + } + break; + case V4L2_CID_CAMERA_ISO: + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) { + err = s5k4ecgx_set_parameter(sd, &parms->iso, + value, "iso", + state->regs->iso, + ARRAY_SIZE(state->regs->iso)); + } else { + dev_err(&client->dev, + "%s: trying to set iso when not " + "in preview mode\n", + __func__); + err = -EINVAL; + } + break; + case V4L2_CID_CAMERA_METERING: + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) { + err = s5k4ecgx_set_parameter(sd, &parms->metering, + value, "metering", + state->regs->metering, + ARRAY_SIZE(state->regs->metering)); + } else { + dev_err(&client->dev, + "%s: trying to set metering when not " + "in preview mode\n", + __func__); + err = -EINVAL; + } + break; + case V4L2_CID_CAMERA_CONTRAST: + err = s5k4ecgx_set_parameter(sd, &parms->contrast, + value, "contrast", + state->regs->contrast, + ARRAY_SIZE(state->regs->contrast)); + break; + case V4L2_CID_CAMERA_SATURATION: + err = s5k4ecgx_set_parameter(sd, &parms->saturation, + value, "saturation", + state->regs->saturation, + ARRAY_SIZE(state->regs->saturation)); + break; + case V4L2_CID_CAMERA_SHARPNESS: + err = s5k4ecgx_set_parameter(sd, &parms->sharpness, + value, "sharpness", + state->regs->sharpness, + ARRAY_SIZE(state->regs->sharpness)); + break; + case V4L2_CID_CAMERA_WDR: + err = s5k4ecgx_set_wdr(sd, value); + break; + case V4L2_CID_CAMERA_FACE_DETECTION: + err = s5k4ecgx_set_face_detection(sd, value); + break; + case V4L2_CID_CAMERA_FOCUS_MODE: + err = s5k4ecgx_set_focus_mode(sd, value); + break; + case V4L2_CID_CAM_JPEG_QUALITY: + if (state->runmode == S5K4ECGX_RUNMODE_RUNNING) { + state->jpeg.quality = value; + err = s5k4ecgx_set_jpeg_quality(sd); + } else { + dev_err(&client->dev, + "%s: trying to set jpeg quality when not " + "in preview mode\n", + __func__); + err = -EINVAL; + } + break; + case V4L2_CID_CAMERA_SCENE_MODE: + err = s5k4ecgx_set_parameter(sd, &parms->scene_mode, + SCENE_MODE_NONE, "scene_mode", + state->regs->scene_mode, + ARRAY_SIZE(state->regs->scene_mode)); + if (err < 0) { + dev_err(&client->dev, + "%s: failed to set scene-mode default value\n", + __func__); + break; + } + if (value != SCENE_MODE_NONE) { + err = s5k4ecgx_set_parameter(sd, &parms->scene_mode, + value, "scene_mode", + state->regs->scene_mode, + ARRAY_SIZE(state->regs->scene_mode)); + } + if (parms->scene_mode == SCENE_MODE_NIGHTSHOT) { + state->one_frame_delay_ms = + NIGHT_MODE_MAX_ONE_FRAME_DELAY_MS; + } else { + state->one_frame_delay_ms = + NORMAL_MODE_MAX_ONE_FRAME_DELAY_MS; + } + + break; + case V4L2_CID_CAMERA_GPS_LATITUDE: + dev_err(&client->dev, + "%s: V4L2_CID_CAMERA_GPS_LATITUDE: not implemented\n", + __func__); + break; + case V4L2_CID_CAMERA_GPS_LONGITUDE: + dev_err(&client->dev, + "%s: V4L2_CID_CAMERA_GPS_LONGITUDE: not implemented\n", + __func__); + break; + case V4L2_CID_CAMERA_GPS_TIMESTAMP: + dev_err(&client->dev, + "%s: V4L2_CID_CAMERA_GPS_TIMESTAMP: not implemented\n", + __func__); + break; + case V4L2_CID_CAMERA_GPS_ALTITUDE: + dev_err(&client->dev, + "%s: V4L2_CID_CAMERA_GPS_ALTITUDE: not implemented\n", + __func__); + break; + case V4L2_CID_CAMERA_OBJECT_POSITION_X: + state->position.x = value; + break; + case V4L2_CID_CAMERA_OBJECT_POSITION_Y: + state->position.y = value; + break; + case V4L2_CID_CAMERA_SET_AUTO_FOCUS: + if (value == AUTO_FOCUS_ON) + err = s5k4ecgx_start_auto_focus(sd); + else if (value == AUTO_FOCUS_OFF) + err = s5k4ecgx_stop_auto_focus(sd); + else { + err = -EINVAL; + dev_err(&client->dev, + "%s: bad focus value requestion %d\n", + __func__, value); + } + break; + case V4L2_CID_CAMERA_FRAME_RATE: + dev_dbg(&client->dev, + "%s: camera frame rate request for %d fps\n", + __func__, value); + err = s5k4ecgx_set_parameter(sd, &parms->fps, + value, "fps", + state->regs->fps, + ARRAY_SIZE(state->regs->fps)); + break; + case V4L2_CID_CAM_CAPTURE: + err = s5k4ecgx_start_capture(sd); + break; + + /* Used to start / stop preview operation. + * This call can be modified to START/STOP operation, + * which can be used in image capture also + */ + case V4L2_CID_CAM_PREVIEW_ONOFF: + if (value) + err = s5k4ecgx_set_preview_start(sd); + else + err = s5k4ecgx_set_preview_stop(sd); + break; + case V4L2_CID_CAMERA_CHECK_DATALINE: + dev_dbg(&client->dev, "%s: check_dataline set to %d\n", + __func__, value); + state->check_dataline = value; + break; + case V4L2_CID_CAMERA_CHECK_DATALINE_STOP: + err = s5k4ecgx_check_dataline_stop(sd); + break; + case V4L2_CID_CAMERA_RETURN_FOCUS: + if (parms->focus_mode != FOCUS_MODE_MACRO) + err = s5k4ecgx_return_focus(sd); + break; + case V4L2_CID_CAMERA_FINISH_AUTO_FOCUS: + err = s5k4ecgx_finish_auto_focus(sd); + break; + default: + dev_err(&client->dev, "%s: unknown set ctrl id 0x%x\n", + __func__, ctrl->id); + err = -ENOIOCTLCMD; + break; + } + + if (err < 0) + dev_err(&client->dev, "%s: videoc_s_ctrl failed %d\n", __func__, + err); + + mutex_unlock(&state->ctrl_lock); + + dev_dbg(&client->dev, "%s: videoc_s_ctrl returning %d\n", + __func__, err); + + return err; +} + +static int s5k4ecgx_s_ext_ctrl(struct v4l2_subdev *sd, + struct v4l2_ext_control *ctrl) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + int err = 0; + struct gps_info_common *tempGPSType = NULL; + + switch (ctrl->id) { + + case V4L2_CID_CAMERA_GPS_LATITUDE: + tempGPSType = (struct gps_info_common *)ctrl->reserved2[1]; + state->gps_info.gps_buf[0] = tempGPSType->direction; + state->gps_info.gps_buf[1] = tempGPSType->dgree; + state->gps_info.gps_buf[2] = tempGPSType->minute; + state->gps_info.gps_buf[3] = tempGPSType->second; + break; + case V4L2_CID_CAMERA_GPS_LONGITUDE: + tempGPSType = (struct gps_info_common *)ctrl->reserved2[1]; + state->gps_info.gps_buf[4] = tempGPSType->direction; + state->gps_info.gps_buf[5] = tempGPSType->dgree; + state->gps_info.gps_buf[6] = tempGPSType->minute; + state->gps_info.gps_buf[7] = tempGPSType->second; + break; + case V4L2_CID_CAMERA_GPS_ALTITUDE: + tempGPSType = (struct gps_info_common *)ctrl->reserved2[1]; + state->gps_info.altitude_buf[0] = tempGPSType->direction; + state->gps_info.altitude_buf[1] = + (tempGPSType->dgree) & 0x00ff; + state->gps_info.altitude_buf[2] = + ((tempGPSType->dgree) & 0xff00) >> 8; + state->gps_info.altitude_buf[3] = tempGPSType->minute; + break; + case V4L2_CID_CAMERA_GPS_TIMESTAMP: + state->gps_info.gps_timeStamp = *((int *)ctrl->reserved2[1]); + err = 0; + break; + default: + dev_err(&client->dev, "%s: unknown ctrl->id %d\n", + __func__, ctrl->id); + err = -ENOIOCTLCMD; + break; + } + + if (err < 0) + dev_err(&client->dev, "%s: vidioc_s_ext_ctrl failed %d\n", + __func__, err); + + return err; +} + +static int s5k4ecgx_s_ext_ctrls(struct v4l2_subdev *sd, + struct v4l2_ext_controls *ctrls) +{ + struct v4l2_ext_control *ctrl = ctrls->controls; + int ret; + int i; + + for (i = 0; i < ctrls->count; i++, ctrl++) { + ret = s5k4ecgx_s_ext_ctrl(sd, ctrl); + + if (ret) { + ctrls->error_idx = i; + break; + } + } + + return ret; +} + +#ifdef CONFIG_VIDEO_S5K4ECGX_DEBUG +static void s5k4ecgx_dump_regset(struct s5k4ecgx_regset *regset) +{ + if ((regset->data[0] == 0x00) && (regset->data[1] == 0x2A)) { + if (regset->size <= 6) + pr_err("odd regset size %d\n", regset->size); + pr_info("regset: addr = 0x%02X%02X, data[0,1] = 0x%02X%02X," + " total data size = %d\n", + regset->data[2], regset->data[3], + regset->data[6], regset->data[7], + regset->size-6); + } else { + pr_info("regset: 0x%02X%02X%02X%02X\n", + regset->data[0], regset->data[1], + regset->data[2], regset->data[3]); + if (regset->size != 4) + pr_err("odd regset size %d\n", regset->size); + } +} +#endif + +static int s5k4ecgx_i2c_write_block(struct v4l2_subdev *sd, u8 *buf, int size) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int retry_count = 5; + int ret; + struct i2c_msg msg = {client->addr, 0, size, buf}; + +#ifdef CONFIG_VIDEO_S5K4ECGX_DEBUG + if (s5k4ecgx_debug_mask & S5K4ECGX_DEBUG_I2C_BURSTS) { + if ((buf[0] == 0x0F) && (buf[1] == 0x12)) + pr_info("%s : data[0,1] = 0x%02X%02X," + " total data size = %d\n", + __func__, buf[2], buf[3], size-2); + else + pr_info("%s : 0x%02X%02X%02X%02X\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + } +#endif + + do { + ret = i2c_transfer(client->adapter, &msg, 1); + if (likely(ret == 1)) + break; + msleep(POLL_TIME_MS); + } while (retry_count-- > 0); + if (ret != 1) { + dev_err(&client->dev, "%s: I2C is not working.\n", __func__); + return -EIO; + } + +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_0 + { + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + if (state->fw.minor == 0) { + /* v1.0 sensor have problems sometimes if we write + * too much data too fast, so add a sleep. I've + * tried various combinations of size/delay. Checking + * for a larger size doesn't seem to work reliably, and + * a delay of 1ms sometimes isn't enough either. + */ + if (size > 16) + msleep(2); + } + } +#endif + return 0; +} + +/* + * Parse the init_reg2 array into a number of register sets that + * we can send over as i2c burst writes instead of writing each + * entry of init_reg2 as a single 4 byte write. Write the + * new data structures and then free them. + */ +static int s5k4ecgx_write_init_reg2_burst(struct v4l2_subdev *sd) +{ + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + struct s5k4ecgx_regset *regset_table; + struct s5k4ecgx_regset *regset; + struct s5k4ecgx_regset *end_regset; + u8 *regset_data; + u8 *dst_ptr; + const u32 *end_src_ptr; + bool flag_copied; + int init_reg_2_array_size = state->regs->init_reg_2.array_size; + int init_reg_2_size = init_reg_2_array_size * sizeof(u32); + const u32 *src_ptr = state->regs->init_reg_2.reg; + u32 src_value; + int err; + + pr_debug("%s : start\n", __func__); + + regset_data = vmalloc(init_reg_2_size); + if (regset_data == NULL) + return -ENOMEM; + regset_table = vmalloc(sizeof(struct s5k4ecgx_regset) * + init_reg_2_size); + if (regset_table == NULL) { + kfree(regset_data); + return -ENOMEM; + } + + dst_ptr = regset_data; + regset = regset_table; + end_src_ptr = &state->regs->init_reg_2.reg[init_reg_2_array_size]; + + src_value = *src_ptr++; + while (src_ptr <= end_src_ptr) { + /* initial value for a regset */ + regset->data = dst_ptr; + flag_copied = false; + *dst_ptr++ = src_value >> 24; + *dst_ptr++ = src_value >> 16; + *dst_ptr++ = src_value >> 8; + *dst_ptr++ = src_value; + + /* check subsequent values for a data flag (starts with + 0x0F12) or something else */ + do { + src_value = *src_ptr++; + if ((src_value & 0xFFFF0000) != 0x0F120000) { + /* src_value is start of next regset */ + regset->size = dst_ptr - regset->data; + regset++; + break; + } + /* copy the 0x0F12 flag if not done already */ + if (!flag_copied) { + *dst_ptr++ = src_value >> 24; + *dst_ptr++ = src_value >> 16; + flag_copied = true; + } + /* copy the data part */ + *dst_ptr++ = src_value >> 8; + *dst_ptr++ = src_value; + } while (src_ptr < end_src_ptr); + } + pr_debug("%s : finished creating table\n", __func__); + + end_regset = regset; + pr_debug("%s : first regset = %p, last regset = %p, count = %d\n", + __func__, regset_table, regset, end_regset - regset_table); + pr_debug("%s : regset_data = %p, end = %p, dst_ptr = %p\n", __func__, + regset_data, regset_data + (init_reg_2_size * sizeof(u32)), + dst_ptr); + +#ifdef CONFIG_VIDEO_S5K4ECGX_DEBUG + if (s5k4ecgx_debug_mask & S5K4ECGX_DEBUG_I2C_BURSTS) { + int last_regset_end_addr = 0; + regset = regset_table; + do { + s5k4ecgx_dump_regset(regset); + if (regset->size > 4) { + int regset_addr = (regset->data[2] << 8 | + regset->data[3]); + if (last_regset_end_addr == regset_addr) + pr_info("%s : this regset can be" + " combined with previous\n", + __func__); + last_regset_end_addr = (regset_addr + + regset->size - 6); + } + regset++; + } while (regset < end_regset); + } +#endif + regset = regset_table; + pr_debug("%s : start writing init reg 2 bursts\n", __func__); + do { + if (regset->size > 4) { + /* write the address packet */ + err = s5k4ecgx_i2c_write_block(sd, regset->data, 4); + if (err) + break; + /* write the data in a burst */ + err = s5k4ecgx_i2c_write_block(sd, regset->data+4, + regset->size-4); + + } else + err = s5k4ecgx_i2c_write_block(sd, regset->data, + regset->size); + if (err) + break; + regset++; + } while (regset < end_regset); + + pr_debug("%s : finished writing init reg 2 bursts\n", __func__); + + vfree(regset_data); + vfree(regset_table); + + return err; +} + +static int s5k4ecgx_init_regs(struct v4l2_subdev *sd) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + u16 read_value; + + /* we'd prefer to do this in probe, but the framework hasn't + * turned on the camera yet so our i2c operations would fail + * if we tried to do it in probe, so we have to do it here + * and keep track if we succeeded or not. + */ + + /* enter read mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x002C, 0x7000); + + s5k4ecgx_i2c_write_twobyte(client, 0x002E, 0x01A6); + s5k4ecgx_i2c_read_twobyte(client, 0x0F12, &read_value); + + pr_info("%s : revision %08X\n", __func__, read_value); + + /* restore write mode */ + s5k4ecgx_i2c_write_twobyte(client, 0x0028, 0x7000); + +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_0 + if (read_value == S5K4ECGX_VERSION_1_0) { + state->regs = ®s_for_fw_version_1_0; + state->initialized = true; + return 0; + } +#endif +#ifdef CONFIG_VIDEO_S5K4ECGX_V_1_1 + if (read_value == S5K4ECGX_VERSION_1_1) { + state->fw.minor = 1; + state->regs = ®s_for_fw_version_1_1; + state->initialized = true; + return 0; + } +#endif + + dev_err(&client->dev, "%s: unknown fw version 0x%x\n", + __func__, read_value); + return -ENODEV; +} + +static int s5k4ecgx_init(struct v4l2_subdev *sd, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + dev_dbg(&client->dev, "%s: start\n", __func__); + + s5k4ecgx_init_parameters(sd); + + if (s5k4ecgx_init_regs(&state->sd) < 0) + return -ENODEV; + + dev_dbg(&client->dev, "%s: state->check_dataline : %d\n", + __func__, state->check_dataline); + + if (s5k4ecgx_set_from_table(sd, "init reg 1", + &state->regs->init_reg_1, 1, 0) < 0) + return -EIO; + + /* delay 10ms after wakeup of SOC processor */ + msleep(10); + + if (s5k4ecgx_write_init_reg2_burst(sd) < 0) + return -EIO; + + if (s5k4ecgx_set_from_table(sd, "flash init", + &state->regs->flash_init, 1, 0) < 0) + return -EIO; + + if (state->check_dataline) { + if (s5k4ecgx_set_from_table(sd, "dtp start", + &state->regs->dtp_start, 1, 0) < 0) + return -EIO; + } + + dev_dbg(&client->dev, "%s: end\n", __func__); + + return 0; +} + +static const struct v4l2_subdev_core_ops s5k4ecgx_core_ops = { + .init = s5k4ecgx_init, /* initializing API */ + .g_ctrl = s5k4ecgx_g_ctrl, + .s_ctrl = s5k4ecgx_s_ctrl, + .s_ext_ctrls = s5k4ecgx_s_ext_ctrls, +}; + +static const struct v4l2_subdev_video_ops s5k4ecgx_video_ops = { + .s_mbus_fmt = s5k4ecgx_s_mbus_fmt, + .enum_framesizes = s5k4ecgx_enum_framesizes, + .enum_mbus_fmt = s5k4ecgx_enum_mbus_fmt, + .try_mbus_fmt = s5k4ecgx_try_mbus_fmt, + .g_parm = s5k4ecgx_g_parm, + .s_parm = s5k4ecgx_s_parm, +}; + +static const struct v4l2_subdev_ops s5k4ecgx_ops = { + .core = &s5k4ecgx_core_ops, + .video = &s5k4ecgx_video_ops, +}; + + +/* + * s5k4ecgx_probe + * Fetching platform data is being done with s_config subdev call. + * In probe routine, we just register subdev device + */ +static int s5k4ecgx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct v4l2_subdev *sd; + struct s5k4ecgx_state *state; + struct s5k4ecgx_platform_data *pdata = client->dev.platform_data; + + if ((pdata == NULL) || (pdata->flash_onoff == NULL)) { + dev_err(&client->dev, "%s: bad platform data\n", __func__); + return -ENODEV; + } + + state = kzalloc(sizeof(struct s5k4ecgx_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + mutex_init(&state->ctrl_lock); + + state->runmode = S5K4ECGX_RUNMODE_NOTREADY; + sd = &state->sd; + strcpy(sd->name, S5K4ECGX_DRIVER_NAME); + + /* + * Assign default format and resolution + * Use configured default information in platform data + * or without them, use default information in driver + */ + state->pix.width = pdata->default_width; + state->pix.height = pdata->default_height; + + if (!pdata->pixelformat) + state->pix.pixelformat = DEFAULT_PIX_FMT; + else + state->pix.pixelformat = pdata->pixelformat; + + if (!pdata->freq) + state->freq = DEFAULT_MCLK; /* 24MHz default */ + else + state->freq = pdata->freq; + + /* Registering subdev */ + v4l2_i2c_subdev_init(sd, client, &s5k4ecgx_ops); + + dev_dbg(&client->dev, "5MP camera S5K4ECGX loaded.\n"); + + return 0; +} + +static int s5k4ecgx_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct s5k4ecgx_state *state = + container_of(sd, struct s5k4ecgx_state, sd); + + v4l2_device_unregister_subdev(sd); + mutex_destroy(&state->ctrl_lock); + kfree(state); + + dev_dbg(&client->dev, "Unloaded camera sensor S5K4ECGX.\n"); + + return 0; +} + +static const struct i2c_device_id s5k4ecgx_id[] = { + { S5K4ECGX_DRIVER_NAME, 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, s5k4ecgx_id); + +static struct i2c_driver v4l2_i2c_driver = { + .driver.name = S5K4ECGX_DRIVER_NAME, + .probe = s5k4ecgx_probe, + .remove = s5k4ecgx_remove, + .id_table = s5k4ecgx_id, +}; + +static int __init v4l2_i2c_drv_init(void) +{ + return i2c_add_driver(&v4l2_i2c_driver); +} + +static void __exit v4l2_i2c_drv_cleanup(void) +{ + i2c_del_driver(&v4l2_i2c_driver); +} + +module_init(v4l2_i2c_drv_init); +module_exit(v4l2_i2c_drv_cleanup); + +MODULE_DESCRIPTION("LSI S5K4ECGX 5MP SOC camera driver"); +MODULE_AUTHOR("Seok-Young Jang <quartz.jang@samsung.com>"); +MODULE_LICENSE("GPL"); + |