Add I2C/V4L2 subdev driver for M-5MOLS integrated image signal processor with 8 Mega Pixel sensor. Signed-off-by: HeungJun, Kim <riverful.kim@xxxxxxxxxxx> Signed-off-by: Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- Hello everyone, This is the eighth version of the subdev for M-5MOLS 8M Pixel camera sensor. The seventh version has a critical bug about missing media_entity_cleanup(), but there is few changes than the previous version. The differences against the previous version is the following: 1. add missing media_entity_cleanup() 2. add the array index check for preventing buffer overrun in m5mols_get_version() 3. use v4l2_dbg/v4l2_err instead of dev_dbg/dev_err 4. rename m5mols_sync_control to m5mols_sync_controls 5. improved description m5mols_start_monitor() function 6. call info->set_power(0) in m5mols_sensor_power when the regulator enable fails And, the re-sending seventh version is the following: http://www.spinics.net/lists/linux-media/msg32319.html I'm sorry about this frequent version up again, but I hope you understand that it was necessary for more perfect code. -- Thanks and Regards, Heungjun Kim Samsung Electronics DMC R&D Center --- drivers/media/video/Kconfig | 2 + drivers/media/video/Makefile | 1 + drivers/media/video/m5mols/Kconfig | 5 + drivers/media/video/m5mols/Makefile | 3 + drivers/media/video/m5mols/m5mols.h | 357 +++++++++ drivers/media/video/m5mols/m5mols_capture.c | 217 ++++++ drivers/media/video/m5mols/m5mols_controls.c | 293 ++++++++ drivers/media/video/m5mols/m5mols_core.c | 1031 ++++++++++++++++++++++++++ drivers/media/video/m5mols/m5mols_reg.h | 321 ++++++++ include/media/m5mols.h | 35 + 10 files changed, 2265 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/m5mols/Kconfig create mode 100644 drivers/media/video/m5mols/Makefile create mode 100644 drivers/media/video/m5mols/m5mols.h create mode 100644 drivers/media/video/m5mols/m5mols_capture.c create mode 100644 drivers/media/video/m5mols/m5mols_controls.c create mode 100644 drivers/media/video/m5mols/m5mols_core.c create mode 100644 drivers/media/video/m5mols/m5mols_reg.h create mode 100644 include/media/m5mols.h diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index d61414e..242c80c 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -753,6 +753,8 @@ config VIDEO_NOON010PC30 ---help--- This driver supports NOON010PC30 CIF camera from Siliconfile +source "drivers/media/video/m5mols/Kconfig" + config VIDEO_OMAP3 tristate "OMAP 3 Camera support (EXPERIMENTAL)" select OMAP_IOMMU diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index a10e4c3..d5d6de1 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/ obj-$(CONFIG_SOC_CAMERA_IMX074) += imx074.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o diff --git a/drivers/media/video/m5mols/Kconfig b/drivers/media/video/m5mols/Kconfig new file mode 100644 index 0000000..302dc3d --- /dev/null +++ b/drivers/media/video/m5mols/Kconfig @@ -0,0 +1,5 @@ +config VIDEO_M5MOLS + tristate "Fujitsu M-5MOLS 8MP sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + ---help--- + This driver supports Fujitsu M-5MOLS camera sensor with ISP diff --git a/drivers/media/video/m5mols/Makefile b/drivers/media/video/m5mols/Makefile new file mode 100644 index 0000000..0a44e02 --- /dev/null +++ b/drivers/media/video/m5mols/Makefile @@ -0,0 +1,3 @@ +m5mols-objs := m5mols_core.o m5mols_controls.o m5mols_capture.o + +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols.o diff --git a/drivers/media/video/m5mols/m5mols.h b/drivers/media/video/m5mols/m5mols.h new file mode 100644 index 0000000..107a8d6 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols.h @@ -0,0 +1,357 @@ +/* + * Header for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@xxxxxxxxxxx + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@xxxxxxxxxxx + * + * 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. + */ + +#ifndef M5MOLS_H +#define M5MOLS_H + +#include <media/v4l2-subdev.h> +#include "m5mols_reg.h" + +extern int m5mols_debug; + +#define M5MOLS_BYTE_READ 0x01 +#define M5MOLS_BYTE_WRITE 0x02 + +enum m5mols_i2c_size { + I2C_8BIT = 1, + I2C_16BIT = 2, + I2C_32BIT = 4, + I2C_MAX = I2C_32BIT, +}; + +/* + * In the MONITOR mode, the M-5MOLS sensor's format is YUV kinds, on the other + * hand, in the CAPTURE mode, its format is available to JPEG, RAW. + */ +enum m5mols_restype { + M5MOLS_RESTYPE_MONITOR, + M5MOLS_RESTYPE_CAPTURE, + M5MOLS_RESTYPE_MAX, +}; + +struct m5mols_resolution { + u8 reg; + enum m5mols_restype type; + u16 width; + u16 height; +}; + +struct m5mols_exif { + u32 exposure_time; + u32 shutter_speed; + u32 aperture; + u32 brightness; + u32 exposure_bias; + u16 iso_speed; + u16 flash; + u16 sdr; /* Subject(object) Distance Range */ + u16 qval; /* Not written exact meanning + * in datasheet */ +}; + +struct m5mols_capture { + struct m5mols_exif exif; + u32 main; + u32 thumb; + u32 total; +}; + +struct m5mols_scenemode { /* Recommended setting in document */ + u32 metering; /* AE light metering */ + u32 ev_bias; /* EV bias */ + u32 wb_mode; /* WhiteBalance(Auto/Manual) */ + u32 wb_preset; /* WhiteBalance Preset */ + u32 chroma_en; /* Chroma Enable */ + u32 chroma_lvl; /* Chroma Level */ + u32 edge_en; /* Edge Enable */ + u32 edge_lvl; /* Edge Level */ + u32 af_range; /* Auto Focus scan range */ + u32 fd_mode; /* Face Detection mode */ + u32 mcc; /* Multi-axis Color Conversion: + * (A.K.A Emotion color) */ + u32 light; /* Light control */ + u32 flash; /* Flash control */ + + /* User setting needed for */ + u32 tone; /* Tone color(contrast) */ + u32 iso; /* ISO */ + u32 capture_mode; /* CAPTURE mode for + * the Image stabilization */ + u32 wdr; /* Wide Dynamic Range */ +}; + +#define VERSION_STRING_SIZE 22 +struct m5mols_version { + u8 customer; /* Customer code: bytes[0] */ + u8 project; /* Project code: bytes[1] */ + u16 fw; /* FirmWare version: bytes[3][2] */ + u16 hw; /* HardWare version: bytes[5][4] */ + u16 param; /* Parameter version: bytes[7][6] */ + u16 awb; /* AWB version: bytes[9][8] */ + u8 str[VERSION_STRING_SIZE]; /* manufacturer & + * packging vendor */ + u8 af; /* AF version: seperate register */ +}; +#define VERSION_SIZE sizeof(struct m5mols_version) + +/* The LSB 2 bytes of version string means packaging manufacturer */ +#define SAMSUNG_ELECTRO "SE" /* Samsung Electro-Mechanics */ +#define SAMSUNG_OPTICS "OP" /* Samsung Fiber-Optics */ +#define SAMSUNG_TECHWIN "TB" /* Samsung Techwin */ + +struct m5mols_info { + const struct m5mols_platform_data *pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt ffmt[M5MOLS_RESTYPE_MAX]; + int curr_res_type; + enum v4l2_mbus_pixelcode code; + wait_queue_head_t wait_capture; + struct work_struct work_irq; + + struct v4l2_ctrl_handler handle; + struct { + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct v4l2_ctrl *autowb; + struct v4l2_ctrl *colorfx; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *zoom; + + struct m5mols_version ver; + struct m5mols_capture cap; + + bool power; + bool ctrl_sync; + bool capture; + bool lock_ae; + bool lock_awb; + + /* Saved register value */ + u8 resolution; + u32 interrupt; + u32 mode; + u32 mode_save; + + /* Optional power callback function dealing with like GPIO. */ + int (*set_power)(struct device *dev, int on); +}; + +/* Helper functions */ +static inline struct m5mols_info *to_m5mols(struct v4l2_subdev *sd) +{ + return container_of(sd, struct m5mols_info, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct m5mols_info, handle)->sd; +} + +static inline bool is_powered(struct m5mols_info *info) +{ + return info->power; +} + +static inline bool is_ctrl_synced(struct m5mols_info *info) +{ + return info->ctrl_sync; +} + +static inline bool is_captured(struct m5mols_info *info) +{ + return info->capture; +} + +static inline bool is_manufacturer(struct m5mols_info *info, u8 *manufacturer) +{ + return (info->ver.str[0] == manufacturer[0] && + info->ver.str[1] == manufacturer[1]) ? + true : false; +} + +#define is_code(__code, __type) (__code == m5mols_default_ffmt[__type].code) + +/* + * m5mols_read_reg() / m5mols_write_reg() + * + * For reading something from M-5MOLS sensor, the I2C operation is needed 2 + * packets, one is for requesting reading and another is for getting data. + * On the other hand, for writing, it's needed just 1. The value's + * order in the packet is much similar in both case reading and writing. + * + * I2C packet common order + * 1st : size (data size + 4) + * 2nd : flag (R:0x01, W:0x02) + * 3rd : category + * 4th : command + * + * Added extra bytes in case reading + * 5th: data + * And read another I2C bytes again as much data size + * + * Added extra bytes in case writing + * 5th..8th: data(as much size of data) + */ +int m5mols_read_reg(struct v4l2_subdev *sd, enum m5mols_i2c_size size, + u8 category, u8 cmd, u32 *val); +int m5mols_write_reg(struct v4l2_subdev *sd, enum m5mols_i2c_size size, + u8 category, u8 cmd, u32 val); +int m5mols_check_busy(struct v4l2_subdev *sd, + u8 category, u8 cmd, u32 value); + +/* + * The m5mols_change_mode() makes the right order executing command for + * changing to the desired operating mode. The three modes(PARAMETER, + * MONITOR, CAPTURE) exist, and only these modes can be changed intentionally + * by user. The each category is assigned in the given mode. + * +============================================================+ + * | mode | category | + * +============================================================+ + * | These mode must be done for using the sensor automatically | + * +------------------------------------------------------------+ + * | FLASH | FLASH(be only after Stand-by or Power-on) | + * | SYSTEM | SYSTEM(be only after sensor arm-booting) | + * +============================================================+ + * | Usually these mode is used on operating sensor | + * +------------------------------------------------------------+ + * | PARAMETER | PARAMETER | + * | MONITOR | MONITOR(preview), Auto Focus, Face Detection | + * | CAPTURE | Single CAPTURE, Preview(recording) | + * +============================================================+ + * + * The possible executing order between each modes is the following: + * ============================================================ + * +-----------+ +---------+ +---------+ + * | PARAMETER |<------->| MONITOR |<-------->| CAPTURE | + * +-----------+ +---------+ +---------+ + */ +int m5mols_change_mode(struct m5mols_info *info, u32 mode); + +int m5mols_change_scenemode(struct m5mols_info *info, u32 mode); +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u32 reg); +int m5mols_start_capture(struct m5mols_info *info); +int m5mols_sync_controls(struct m5mols_info *info); +int m5mols_lock_3a(struct m5mols_info *info, bool lock); +int m5mols_set_ctrl(struct v4l2_ctrl *ctrl); + +static inline int __must_check +i2c_w8_system(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_SYSTEM, cmd, val); +} + +static inline int __must_check +i2c_w8_param(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_PARAM, cmd, val); +} + +static inline int __must_check +i2c_w8_mon(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_MONITOR, cmd, val); +} + +static inline int __must_check +i2c_w8_ae(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_AE, cmd, val); +} + +static inline int __must_check +i2c_w16_ae(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_16BIT, CAT_AE, cmd, val); +} + +static inline int __must_check +i2c_w8_wb(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_WB, cmd, val); +} + +static inline int __must_check +i2c_w8_fd(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_FD, cmd, val); +} + +static inline int __must_check +i2c_w8_lens(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_LENS, cmd, val); +} + +static inline int __must_check +i2c_w8_capt_param(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_CAPTURE_PARAMETER, cmd, val); +} + +static inline int __must_check +i2c_w8_capt_ctrl(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_CAPTURE_CONTROL, cmd, val); +} + +static inline int __must_check +i2c_w8_flash(struct v4l2_subdev *sd, u8 cmd, u32 val) +{ + return m5mols_write_reg(sd, I2C_8BIT, CAT_FLASH, cmd, val); +} + +static inline int __must_check +i2c_r8_system(struct v4l2_subdev *sd, u8 cmd, u32 *val) +{ + return m5mols_read_reg(sd, I2C_8BIT, CAT_SYSTEM, cmd, val); +} + +static inline int __must_check +i2c_r16_ae(struct v4l2_subdev *sd, u8 cmd, u32 *val) +{ + return m5mols_read_reg(sd, I2C_16BIT, CAT_AE, cmd, val); +} + +static inline int __must_check +i2c_r8_lens(struct v4l2_subdev *sd, u8 cmd, u32 *val) +{ + return m5mols_read_reg(sd, I2C_8BIT, CAT_LENS, cmd, val); +} + +static inline int __must_check +i2c_r32_capt_ctrl(struct v4l2_subdev *sd, u8 cmd, u32 *val) +{ + return m5mols_read_reg(sd, I2C_32BIT, CAT_CAPTURE_CONTROL, cmd, val); +} + +static inline int __must_check +i2c_r16_exif(struct v4l2_subdev *sd, u8 cmd, u32 *val) +{ + return m5mols_read_reg(sd, I2C_16BIT, CAT_EXIF, cmd, val); +} + +static inline int __must_check +i2c_r32_exif(struct v4l2_subdev *sd, u8 cmd, u32 *val) +{ + return m5mols_read_reg(sd, I2C_32BIT, CAT_EXIF, cmd, val); +} + +/* The firmware function */ +int m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)); + +#endif /* M5MOLS_H */ diff --git a/drivers/media/video/m5mols/m5mols_capture.c b/drivers/media/video/m5mols/m5mols_capture.c new file mode 100644 index 0000000..f2f1045 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_capture.c @@ -0,0 +1,217 @@ +/* + * The Capture code for Fujitsu M-5MOLS ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@xxxxxxxxxxx + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@xxxxxxxxxxx + * + * 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/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/version.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <linux/version.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/m5mols.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +static int m5mols_capture_error_handler(struct m5mols_info *info, + int timeout) +{ + int ret; + + /* Disable all interrupt & clear desired interrupt */ + ret = i2c_w8_system(&info->sd, CAT0_INT_ENABLE, + info->interrupt & ~(REG_INT_CAPTURE)); + if (ret) + return ret; + + /* If all timeout exhausted, return error. */ + if (!timeout) + return -ETIMEDOUT; + + /* Clear capture */ + if (!ret) + info->capture = false; + + return 0; +} + +/* m5mols_capture_info() - Gather captured image informations. For now, + * it gathers only EXIF information and file size. */ +static int m5mols_capture_info(struct m5mols_info *info, bool msgon) +{ + struct v4l2_subdev *sd = &info->sd; + struct m5mols_exif *exif = &info->cap.exif; + int denominator, numerator; + int ret; + + ret = i2c_r32_exif(sd, CAT7_INFO_EXPTIME_NU, &numerator); + if (!ret) + ret = i2c_r32_exif(sd, CAT7_INFO_EXPTIME_DE, &denominator); + if (!ret) + exif->exposure_time = (u32)(numerator / denominator); + if (ret) + return ret; + + ret = i2c_r32_exif(sd, CAT7_INFO_TV_NU, &numerator); + if (!ret) + ret = i2c_r32_exif(sd, CAT7_INFO_TV_DE, &denominator); + if (!ret) + exif->shutter_speed = (u32)(numerator / denominator); + if (ret) + return ret; + + ret = i2c_r32_exif(sd, CAT7_INFO_AV_NU, &numerator); + if (!ret) + ret = i2c_r32_exif(sd, CAT7_INFO_AV_DE, &denominator); + if (!ret) + exif->aperture = (u32)(numerator / denominator); + if (ret) + return ret; + + ret = i2c_r32_exif(sd, CAT7_INFO_BV_NU, &numerator); + if (!ret) + ret = i2c_r32_exif(sd, CAT7_INFO_BV_DE, &denominator); + if (!ret) + exif->brightness = (u32)(numerator / denominator); + if (ret) + return ret; + + ret = i2c_r32_exif(sd, CAT7_INFO_EBV_NU, &numerator); + if (!ret) + ret = i2c_r32_exif(sd, CAT7_INFO_EBV_DE, &denominator); + if (!ret) + exif->exposure_bias = (u32)(numerator / denominator); + if (ret) + return ret; + + ret = i2c_r16_exif(sd, CAT7_INFO_ISO, (u32 *)&exif->iso_speed); + if (!ret) + ret = i2c_r16_exif(sd, CAT7_INFO_FLASH, (u32 *)&exif->flash); + if (!ret) + ret = i2c_r16_exif(sd, CAT7_INFO_SDR, (u32 *)&exif->sdr); + if (!ret) + ret = i2c_r16_exif(sd, CAT7_INFO_QVAL, (u32 *)&exif->qval); + if (ret) + return ret; + + if (!ret) + ret = i2c_r32_capt_ctrl(sd, CATC_CAP_IMAGE_SIZE, + &info->cap.main); + if (!ret) + ret = i2c_r32_capt_ctrl(sd, CATC_CAP_THUMB_SIZE, + &info->cap.thumb); + if (ret) + return ret; + + info->cap.total = info->cap.main + info->cap.thumb; + + if (msgon) { + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *cdev = &client->dev; + + dev_info(cdev, "capture: total size\t%d\n", info->cap.total); + dev_info(cdev, "capture: main size\t%d\n", info->cap.main); + dev_info(cdev, "capture: thumb size\t%d\n", info->cap.thumb); + dev_info(cdev, "capture: exposure_time\t%d\n", + exif->exposure_time); + dev_info(cdev, "capture: shutter_speed\t%d\n", + exif->shutter_speed); + dev_info(cdev, "capture: aperture\t%d\n", exif->aperture); + dev_info(cdev, "capture: brightness\t%d\n", exif->brightness); + dev_info(cdev, "capture: exposure_bias\t%d\n", + exif->exposure_bias); + dev_info(cdev, "capture: iso_speed\t%d\n", exif->iso_speed); + dev_info(cdev, "capture: flash\t%d\n", exif->flash); + dev_info(cdev, "capture: sdr\t%d\n", exif->sdr); + dev_info(cdev, "capture: qval\t%d\n", exif->qval); + } + + return ret; +} + +int m5mols_start_capture(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + u32 resolution = info->resolution; + int timeout; + int ret; + + /* + * Preparing capture. Setting control & interrupt before entering + * capture mode + * + * 1) change to MONITOR mode for operating control & interrupt + * 2) set controls (considering v4l2_control value & lock 3A) + * 3) set interrupt + * 4) change to CAPTURE mode + */ + ret = m5mols_change_mode(info, REG_MODE_MONITOR); + if (!ret) + ret = m5mols_sync_controls(info); + if (!ret) + ret = m5mols_lock_3a(info, true); + if (!ret) + ret = m5mols_enable_interrupt(sd, REG_INT_CAPTURE); + if (!ret) + ret = m5mols_change_mode(info, REG_MODE_CAPTURE); + if (!ret) + /* Wait for capture interrupt, after changing capture mode */ + timeout = wait_event_interruptible_timeout(info->wait_capture, + is_captured(info), msecs_to_jiffies(2000)); + if (!ret && is_captured(info)) + ret = m5mols_capture_error_handler(info, timeout); + if (!ret) + ret = m5mols_lock_3a(info, false); + if (ret) + return ret; + + /* + * Starting capture. Setting capture frame count and resolution and + * the format(available format: JPEG, Bayer RAW, YUV). + * + * 1) select single or multi(enable to 25), format, size + * 2) set interrupt + * 3) start capture(for main image, now) + * 4) get information + * 5) notify file size to v4l2 device(e.g, to s5p-fimc v4l2 device) + */ + ret = i2c_w8_capt_ctrl(sd, CATC_CAP_SEL_FRAME, 1); + if (!ret) + ret = i2c_w8_capt_param(sd, CATB_YUVOUT_MAIN, REG_JPEG); + if (!ret) + ret = i2c_w8_capt_param(sd, CATB_MAIN_IMAGE_SIZE, resolution); + if (!ret) + ret = m5mols_enable_interrupt(sd, REG_INT_CAPTURE); + if (!ret) + ret = i2c_w8_capt_ctrl(sd, CATC_CAP_START, REG_CAP_START_MAIN); + if (!ret) + /* Wait for capture interrupt, after starting capture */ + timeout = wait_event_interruptible_timeout(info->wait_capture, + is_captured(info), msecs_to_jiffies(2000)); + if (!ret && is_captured(info)) + ret = m5mols_capture_info(info, false); + if (!ret) + v4l2_subdev_notify(sd, 0, &info->cap.total); + if (!ret) + ret = m5mols_capture_error_handler(info, timeout); + + return ret; +} diff --git a/drivers/media/video/m5mols/m5mols_controls.c b/drivers/media/video/m5mols/m5mols_controls.c new file mode 100644 index 0000000..1eb909e --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_controls.c @@ -0,0 +1,293 @@ +/* + * Controls for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@xxxxxxxxxxx + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@xxxxxxxxxxx + * + * 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/videodev2.h> +#include <media/v4l2-ctrls.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +static struct m5mols_scenemode m5mols_default_scenemode[] = { + [REG_SCENE_NORMAL] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_NORMAL, REG_LIGHT_OFF, REG_FLASH_OFF, + 5, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PORTRAIT] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 4, + REG_AF_MODE_NORMAL, BIT_FD_EN | BIT_FD_DRAW_FACE_FRAME, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_LANDSCAPE] = { + REG_AE_MODE_ALL, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 6, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SPORTS] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PARTY_INDOOR] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_200, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_BEACH_SNOW] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_10_POS, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SUNSET] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_PRESET, + REG_AWB_DAYLIGHT, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_DAWN_DUSK] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_PRESET, + REG_AWB_FLUORESCENT_1, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FALL] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 5, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_NIGHT] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_AGAINST_LIGHT] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FIRE] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, + [REG_SCENE_TEXT] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 7, + REG_AF_MODE_MACRO, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_ANTI_SHAKE, REG_WDR_ON, + }, + [REG_SCENE_CANDLE] = { + REG_AE_MODE_CENTER, REG_AE_INDEX_00, REG_AWB_MODE_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_MODE_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_MODE_NONE, REG_WDR_OFF, + }, +}; + +/* m5mols_change_scenemode() - Change current scenemode */ +int m5mols_change_scenemode(struct m5mols_info *info, u32 mode) +{ + struct v4l2_subdev *sd = &info->sd; + struct m5mols_scenemode scenemode = m5mols_default_scenemode[mode]; + int ret; + + ret = m5mols_lock_3a(info, false); + if (!ret) + ret = i2c_w8_ae(sd, CAT3_EV_PRESET_MODE_MON, mode); + if (!ret) + ret = i2c_w8_ae(sd, CAT3_EV_PRESET_MODE_CAP, mode); + if (!ret) + ret = i2c_w8_ae(sd, CAT3_AE_MODE, scenemode.metering); + if (!ret) + ret = i2c_w8_ae(sd, CAT3_AE_INDEX, scenemode.ev_bias); + if (!ret) + ret = i2c_w8_wb(sd, CAT6_AWB_MODE, scenemode.wb_mode); + if (!ret) + ret = i2c_w8_wb(sd, CAT6_AWB_MANUAL, scenemode.wb_preset); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_CHROMA_EN, scenemode.chroma_en); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_CHROMA_LVL, scenemode.chroma_lvl); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_EDGE_EN, scenemode.edge_en); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_EDGE_LVL, scenemode.edge_lvl); + if (!ret && info->ver.af) + ret = i2c_w8_lens(sd, CATA_AF_MODE, scenemode.af_range); + if (!ret && info->ver.af) + ret = i2c_w8_fd(sd, CAT9_FD_CTL, scenemode.fd_mode); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_TONE_CTL, scenemode.tone); + if (!ret) + ret = i2c_w8_mon(sd, CAT3_ISO, scenemode.iso); + if (!ret) + ret = m5mols_change_mode(info, REG_MODE_CAPTURE); + if (!ret) + ret = i2c_w8_mon(sd, CATB_WDR_EN, scenemode.wdr); + if (!ret) + ret = i2c_w8_capt_param(sd, CATB_MCC_MODE, scenemode.mcc); + if (!ret) + ret = i2c_w8_capt_param(sd, CATB_LIGHT_CTRL, scenemode.light); + if (!ret) + ret = i2c_w8_capt_param(sd, CATB_FLASH_CTRL, scenemode.flash); + if (!ret) + ret = i2c_w8_capt_param(sd, CATB_FLASH_CTRL, scenemode.flash); + if (!ret) + ret = i2c_w8_mon(sd, CATC_CAP_MODE, scenemode.capture_mode); + if (!ret) + ret = m5mols_change_mode(info, REG_MODE_MONITOR); + + return ret; +} + +static int m5mols_lock_ae(struct m5mols_info *info, bool lock) +{ + int ret = 0; + + if (info->lock_ae != lock) + ret = i2c_w8_ae(&info->sd, CAT3_AE_LOCK, + lock ? REG_AE_LOCK : REG_AE_UNLOCK); + if (!ret) + info->lock_ae = lock; + + return ret; +} + +static int m5mols_lock_awb(struct m5mols_info *info, bool lock) +{ + int ret = 0; + + if (info->lock_awb != lock) + ret = i2c_w8_wb(&info->sd, CAT6_AWB_LOCK, + lock ? REG_AWB_LOCK : REG_AWB_UNLOCK); + if (!ret) + info->lock_awb = lock; + + return ret; +} + +/* m5mols_lock_3a() - Lock 3A(Auto Exposure, Whitebalance, Focus) */ +int m5mols_lock_3a(struct m5mols_info *info, bool lock) +{ + int ret; + + ret = m5mols_lock_ae(info, lock); + if (!ret) + ret = m5mols_lock_awb(info, lock); + if (!ret && info->ver.af && lock) + /* Don't need to handle unlocking AF */ + ret = i2c_w8_lens(&info->sd, CATA_AF_EXECUTE, REG_AF_STOP); + + return ret; +} + +/* m5mols_set_ctrl() - The main s_ctrl function called by m5mols_set_ctrl() */ +int m5mols_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int ret; + + switch (ctrl->id) { + case V4L2_CID_ZOOM_ABSOLUTE: + return i2c_w8_mon(sd, CAT2_ZOOM, ctrl->val); + + case V4L2_CID_EXPOSURE_AUTO: + ret = m5mols_lock_ae(info, ctrl->val == V4L2_EXPOSURE_AUTO ? + false : true); + if (!ret) + ret = i2c_w8_ae(sd, CAT3_AE_MODE, + ctrl->val == V4L2_EXPOSURE_AUTO ? + REG_AE_MODE_ALL : REG_AE_MODE_OFF); + if (!ret && ctrl->val == V4L2_EXPOSURE_MANUAL) + ret = i2c_w16_ae(sd, CAT3_MANUAL_GAIN_MON, + info->exposure->val); + if (!ret && ctrl->val == V4L2_EXPOSURE_MANUAL) + ret = i2c_w16_ae(sd, CAT3_MANUAL_GAIN_CAP, + info->exposure->val); + return ret; + + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = m5mols_lock_awb(info, ctrl->val ? false : true); + if (!ret) + ret = i2c_w8_wb(sd, CAT6_AWB_MODE, ctrl->val ? + REG_AWB_MODE_AUTO : REG_AWB_MODE_PRESET); + return ret; + + case V4L2_CID_SATURATION: + ret = i2c_w8_mon(sd, CAT2_CHROMA_LVL, ctrl->val); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_CHROMA_EN, REG_CHROMA_ON); + return ret; + + case V4L2_CID_COLORFX: + /* + * This control uses two kinds of registers: normal & color. + * The normal effect belongs to category 1, while the color + * one belongs to category 2. + * + * The normal effect uses one register: CAT1_EFFECT. + * The color effect uses three registers: + * CAT2_COLOR_EFFECT, CAT2_CFIXR, CAT2_CFIXB. + */ + ret = i2c_w8_param(sd, CAT1_EFFECT, + ctrl->val == V4L2_COLORFX_NEGATIVE ? REG_EFFECT_NEGA : + ctrl->val == V4L2_COLORFX_EMBOSS ? REG_EFFECT_EMBOSS : + REG_EFFECT_OFF); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_COLOR_EFFECT, + ctrl->val == V4L2_COLORFX_SEPIA ? + REG_COLOR_EFFECT_ON : REG_COLOR_EFFECT_OFF); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_CFIXR, + ctrl->val == V4L2_COLORFX_SEPIA ? + REG_CFIXR_SEPIA : 0); + if (!ret) + ret = i2c_w8_mon(sd, CAT2_CFIXB, + ctrl->val == V4L2_COLORFX_SEPIA ? + REG_CFIXB_SEPIA : 0); + return ret; + } + + return -EINVAL; +} diff --git a/drivers/media/video/m5mols/m5mols_core.c b/drivers/media/video/m5mols/m5mols_core.c new file mode 100644 index 0000000..85ec068 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_core.c @@ -0,0 +1,1031 @@ +/* + * Driver for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@xxxxxxxxxxx + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@xxxxxxxxxxx + * + * 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/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/version.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/m5mols.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +int m5mols_debug; +module_param(m5mols_debug, int, 0644); + +#define MOD_NAME "M5MOLS" +#define M5MOLS_I2C_CHECK_RETRY 500 + +/* The regulator consumer names are derived from M-5MOLS datasheets. */ +static struct regulator_bulk_data supplies[] = { + { + .supply = "core", /* ARM core power, 1.2V */ + }, { + .supply = "dig_18", /* digital power 1, 1.8V */ + }, { + .supply = "d_sensor", /* sensor power 1, 1.8V */ + }, { + .supply = "dig_28", /* digital power 2, 2.8V */ + }, { + .supply = "a_sensor", /* analog power */ + }, { + .supply = "dig_12", /* digital power 3, 1.2V */ + }, +}; + +const struct v4l2_mbus_framefmt m5mols_default_ffmt[M5MOLS_RESTYPE_MAX] = { + [M5MOLS_RESTYPE_MONITOR] = { + .width = 1920, + .height = 1080, + .code = V4L2_MBUS_FMT_VYUY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + [M5MOLS_RESTYPE_CAPTURE] = { + .width = 1920, + .height = 1080, + .code = V4L2_MBUS_FMT_JPEG_1X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; +#define SIZE_DEFAULT_FFMT ARRAY_SIZE(m5mols_default_ffmt) + +static const struct m5mols_resolution m5mols_reg_res[] = { + { 0x01, M5MOLS_RESTYPE_MONITOR, 128, 96 }, /* SUB-QCIF */ + { 0x03, M5MOLS_RESTYPE_MONITOR, 160, 120 }, /* QQVGA */ + { 0x05, M5MOLS_RESTYPE_MONITOR, 176, 144 }, /* QCIF */ + { 0x06, M5MOLS_RESTYPE_MONITOR, 176, 176 }, + { 0x08, M5MOLS_RESTYPE_MONITOR, 240, 320 }, /* QVGA */ + { 0x09, M5MOLS_RESTYPE_MONITOR, 320, 240 }, /* QVGA */ + { 0x0c, M5MOLS_RESTYPE_MONITOR, 240, 400 }, /* WQVGA */ + { 0x0d, M5MOLS_RESTYPE_MONITOR, 400, 240 }, /* WQVGA */ + { 0x0e, M5MOLS_RESTYPE_MONITOR, 352, 288 }, /* CIF */ + { 0x13, M5MOLS_RESTYPE_MONITOR, 480, 360 }, + { 0x15, M5MOLS_RESTYPE_MONITOR, 640, 360 }, /* qHD */ + { 0x17, M5MOLS_RESTYPE_MONITOR, 640, 480 }, /* VGA */ + { 0x18, M5MOLS_RESTYPE_MONITOR, 720, 480 }, + { 0x1a, M5MOLS_RESTYPE_MONITOR, 800, 480 }, /* WVGA */ + { 0x1f, M5MOLS_RESTYPE_MONITOR, 800, 600 }, /* SVGA */ + { 0x21, M5MOLS_RESTYPE_MONITOR, 1280, 720 }, /* HD */ + { 0x25, M5MOLS_RESTYPE_MONITOR, 1920, 1080 }, /* 1080p */ + { 0x29, M5MOLS_RESTYPE_MONITOR, 3264, 2448 }, /* 2.63fps 8M */ + { 0x39, M5MOLS_RESTYPE_MONITOR, 800, 602 }, /* AHS_MON debug */ + + { 0x02, M5MOLS_RESTYPE_CAPTURE, 320, 240 }, /* QVGA */ + { 0x04, M5MOLS_RESTYPE_CAPTURE, 400, 240 }, /* WQVGA */ + { 0x07, M5MOLS_RESTYPE_CAPTURE, 480, 360 }, + { 0x08, M5MOLS_RESTYPE_CAPTURE, 640, 360 }, /* qHD */ + { 0x09, M5MOLS_RESTYPE_CAPTURE, 640, 480 }, /* VGA */ + { 0x0a, M5MOLS_RESTYPE_CAPTURE, 800, 480 }, /* WVGA */ + { 0x10, M5MOLS_RESTYPE_CAPTURE, 1280, 720 }, /* HD */ + { 0x14, M5MOLS_RESTYPE_CAPTURE, 1280, 960 }, /* 1M */ + { 0x17, M5MOLS_RESTYPE_CAPTURE, 1600, 1200 }, /* 2M */ + { 0x19, M5MOLS_RESTYPE_CAPTURE, 1920, 1080 }, /* Full-HD */ + { 0x1a, M5MOLS_RESTYPE_CAPTURE, 2048, 1152 }, /* 3Mega */ + { 0x1b, M5MOLS_RESTYPE_CAPTURE, 2048, 1536 }, + { 0x1c, M5MOLS_RESTYPE_CAPTURE, 2560, 1440 }, /* 4Mega */ + { 0x1d, M5MOLS_RESTYPE_CAPTURE, 2560, 1536 }, + { 0x1f, M5MOLS_RESTYPE_CAPTURE, 2560, 1920 }, /* 5Mega */ + { 0x21, M5MOLS_RESTYPE_CAPTURE, 3264, 1836 }, /* 6Mega */ + { 0x22, M5MOLS_RESTYPE_CAPTURE, 3264, 1960 }, + { 0x25, M5MOLS_RESTYPE_CAPTURE, 3264, 2448 }, /* 8Mega */ +}; + +/* + * m5mols_swap_byte() - byte array to integer conversion + * @size: the size's kinds of I2C packet defined in the M-5MOLS datasheet + * + * Convert I2C data byte array with performing any required byte + * reordering to assure proper values for each data type, regardless + * of the architecture endianness. + */ +static u32 m5mols_swap_byte(u8 *data, enum m5mols_i2c_size size) +{ + if (size == I2C_8BIT) + return *data; + else if (size == I2C_16BIT) + return be16_to_cpu(*((u16 *)data)); + else + return be32_to_cpu(*((u32 *)data)); +} + +/* + * m5mols_read_reg() - raw function of I2C reading operation + * @size: the kinds of I2C packet's size defined in the M-5MOLS datasheet + * @category: the category value of I2C command + * @cmd: the command + * @val: the read values + * + * The M-5MOLS I2C operation always consists of category, command, value(s) + * as an element. The numbers of each operation vary according to reading or + * writing. The more specific explanation can be found in the m5mol.h. + */ +int m5mols_read_reg(struct v4l2_subdev *sd, + enum m5mols_i2c_size size, + u8 category, u8 cmd, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_msg msg[2]; + u8 wbuf[5], rbuf[I2C_MAX + 1]; + int ret; + + if (!client->adapter) + return -ENODEV; + + if (size != I2C_8BIT && size != I2C_16BIT && size != I2C_32BIT) + return -EINVAL; + + /* for designating category and command */ + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 5; + msg[0].buf = wbuf; + wbuf[0] = 5; + wbuf[1] = M5MOLS_BYTE_READ; + wbuf[2] = category; + wbuf[3] = cmd; + wbuf[4] = size; + + /* for reading desired data */ + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = size + 1; + msg[1].buf = rbuf; + + /* minimum stabilization time */ + usleep_range(200, 200); + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, + "failed READ[%d] at cat[%02x] cmd[%02x]\n", + size, category, cmd); + return ret; + } + + *val = m5mols_swap_byte(&rbuf[1], size); + + return 0; +} + +/* + * m5mols_write_reg() - raw function of I2C writing operation + * @size: the kinds of I2C packet's size defined in the M-5MOLS datasheet + * @category: the category value of I2C command + * @cmd: the I2C command + * @val: the written value(s) + */ +int m5mols_write_reg(struct v4l2_subdev *sd, + enum m5mols_i2c_size size, + u8 category, u8 cmd, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct device *cdev = &client->dev; + struct i2c_msg msg[1]; + u8 wbuf[I2C_MAX + 4]; + u32 *buf; + int ret; + + if (!client->adapter) + return -ENODEV; + + if (size != I2C_8BIT && size != I2C_16BIT && size != I2C_32BIT) { + dev_err(cdev, "Wrong data size\n"); + return -EINVAL; + } + + msg->addr = client->addr; + msg->flags = 0; + msg->len = (u16)size + 4; + msg->buf = wbuf; + wbuf[0] = size + 4; + wbuf[1] = M5MOLS_BYTE_WRITE; + wbuf[2] = category; + wbuf[3] = cmd; + + buf = (u32 *)&wbuf[4]; + *buf = m5mols_swap_byte((u8 *)&val, size); + + usleep_range(200, 200); + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) { + dev_err(&client->dev, + "failed WRITE[%d] at cat[%02x] cmd[%02x], ret %d\n", + size, msg->buf[2], msg->buf[3], ret); + return ret; + } + + return 0; +} + +int m5mols_check_busy(struct v4l2_subdev *sd, u8 category, u8 cmd, u32 mask) +{ + u32 busy, i; + int ret; + + for (i = 0; i < M5MOLS_I2C_CHECK_RETRY; i++) { + ret = m5mols_read_reg(sd, I2C_8BIT, category, cmd, &busy); + if (ret < 0) + return ret; + + if ((busy & mask) == mask) + return 0; + } + + return -EBUSY; +} + +/* + * m5mols_enable_interrupt() - clear pending register and set interrupt + * + * Before writing desired interrupt value, should clear INT_FACTOR register + * automatically documented in the M-5MOLS datasheet. + */ +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u32 reg) +{ + struct m5mols_info *info = to_m5mols(sd); + u32 dummy; + int ret; + + ret = i2c_r8_system(sd, CAT0_INT_FACTOR, &dummy); + if (!ret) + ret = i2c_w8_system(sd, CAT0_INT_ENABLE, + info->ver.af ? reg & ~REG_INT_AF : reg); + return ret; +} + +/* + * m5mols_write_mode() - Write the mode and check busy status. + * + * Changing the M-5MOLS mode accompanies a little delay all the time, so + * checking current busy status is needed to guarantee changing right mode. + */ +static int m5mols_write_mode(struct v4l2_subdev *sd, u32 mode) +{ + int ret = i2c_w8_system(sd, CAT0_SYSMODE, mode); + if (!ret) + ret = m5mols_check_busy(sd, CAT_SYSTEM, CAT0_SYSMODE, mode); + + return ret; +} + +/* + * m5mols_change_mode() - manage the M-5MOLS's mode. + * @mode: the desired mode + * + * All commands of M-5MOLS belong to a specific its own mode. Each functionality + * can be guaranteed only when the sensor is operating in the mode which + * the command belongs to. The more explanation can be found in the m5mols.h. + */ +int m5mols_change_mode(struct m5mols_info *info, u32 mode) +{ + struct v4l2_subdev *sd = &info->sd; + int ret = -EINVAL; + u32 reg; + + if (mode != REG_MODE_PARAM && mode != REG_MODE_MONITOR && + mode != REG_MODE_CAPTURE) + return ret; + + ret = i2c_r8_system(sd, CAT0_SYSMODE, ®); + if ((!ret && reg == mode) || ret) + return ret; + + switch (reg) { + case REG_MODE_PARAM: + ret = m5mols_write_mode(sd, REG_MODE_MONITOR); + if (!ret && mode == REG_MODE_MONITOR) + break; + if (!ret) + ret = m5mols_write_mode(sd, REG_MODE_CAPTURE); + break; + + case REG_MODE_MONITOR: + if (mode == REG_MODE_PARAM) { + ret = m5mols_write_mode(sd, REG_MODE_PARAM); + break; + } + + ret = m5mols_write_mode(sd, REG_MODE_CAPTURE); + break; + + case REG_MODE_CAPTURE: + ret = m5mols_write_mode(sd, REG_MODE_MONITOR); + if (!ret && mode == REG_MODE_MONITOR) + break; + if (!ret) + ret = m5mols_write_mode(sd, REG_MODE_PARAM); + break; + + default: + v4l2_warn(sd, "Wrong mode: %d\n", mode); + } + + if (!ret) + info->mode = mode; + + return ret; +} + +/* + * m5mols_get_version() - Retrieve all internal version of M-5MOLS. + * + * The version information includes revisions hardware and firmware, AutoFocus + * alghorithm version and the version string. The version string should be read + * at the same register offset until the read value is NULL. + */ +static int m5mols_get_version(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + union { + struct m5mols_version ver; + u8 bytes[VERSION_SIZE]; + } value; + int ret, cmd = CAT0_CUSTOMER_CODE; + + do { + ret = i2c_r8_system(sd, cmd, (u32 *)&value.bytes[cmd]); + if (ret) + return ret; + } while (cmd++ != CAT0_VERSION_AWB); + + do { + ret = i2c_r8_system(sd, CAT0_VERSION_STRING, + (u32 *)&value.bytes[cmd]); + if (ret) + return ret; + if (cmd >= VERSION_SIZE - 1) + return -EINVAL; + } while (value.bytes[cmd++]); + + ret = i2c_r8_lens(sd, CATA_AF_VERSION, (u32 *)&value.bytes[cmd]); + if (ret) + return ret; + + /* store version information swapped for being readable */ + info->ver = value.ver; + info->ver.fw = be16_to_cpu(info->ver.fw); + info->ver.hw = be16_to_cpu(info->ver.hw); + info->ver.param = be16_to_cpu(info->ver.param); + info->ver.awb = be16_to_cpu(info->ver.awb); + + v4l2_info(sd, "Manufacturer\t[%s]\n", + is_manufacturer(info, SAMSUNG_ELECTRO) ? + "Samsung Electro-Machanics" : + is_manufacturer(info, SAMSUNG_OPTICS) ? + "Samsung Fiber-Optics" : + is_manufacturer(info, SAMSUNG_TECHWIN) ? + "Samsung Techwin" : "None"); + v4l2_info(sd, "Customer/Project\t[0x%02x/0x%02x]\n", + info->ver.customer, info->ver.project); + if (!info->ver.af) + v4l2_info(sd, "No support Auto Focus on this firmware\n"); + + return ret; +} + +/* + * __find_restype() - look up M-5MOLS resolution type according to pixel code + * @code: pixel code + */ +static enum m5mols_restype __find_restype(enum v4l2_mbus_pixelcode code) +{ + enum m5mols_restype type = M5MOLS_RESTYPE_MONITOR; + + do { + if (code == m5mols_default_ffmt[type].code) + return type; + } while (type++ != SIZE_DEFAULT_FFMT); + + return 0; +} + +/* + * __find_resolution() - look up preset and type of M-5MOLS's resolution + * @mf: pixel format to find/negotiate the resolution preset for + * @type: M-5MOLS resolution type + * @resolution: M-5MOLS resolution preset register value + * + * Find nearest resolution matching resolution preset and adjust mf + * to supported values. + */ +static int __find_resolution(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf, + enum m5mols_restype *type, + u32 *resolution) +{ + const struct m5mols_resolution *fsize = &m5mols_reg_res[0], + *match = NULL; + enum m5mols_restype stype = __find_restype(mf->code); + int i = ARRAY_SIZE(m5mols_reg_res); + unsigned int min_err = ~0; + + while (i--) { + int err; + if (stype != fsize->type) + continue; + + err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + *resolution = match->reg; + *type = stype; + return 0; + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt *__find_format(struct m5mols_info *info, + struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which, + enum m5mols_restype type) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return fh ? v4l2_subdev_get_try_format(fh, 0) : NULL; + + return &info->ffmt[type]; +} + +static int m5mols_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format; + + if (fmt->pad != 0) + return -EINVAL; + + format = __find_format(info, fh, fmt->which, info->curr_res_type); + if (!format) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +static int m5mols_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format = &fmt->format; + struct v4l2_mbus_framefmt *sfmt; + enum m5mols_restype type; + u32 resolution = 0; + int ret; + + if (fmt->pad != 0) + return -EINVAL; + + ret = __find_resolution(sd, format, &type, &resolution); + if (ret < 0) + return ret; + + sfmt = __find_format(info, fh, fmt->which, type); + if (!sfmt) + return 0; + + *sfmt = m5mols_default_ffmt[type]; + sfmt->width = format->width; + sfmt->height = format->height; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + info->resolution = resolution; + info->code = format->code; + info->curr_res_type = type; + } + + return 0; +} + +static int m5mols_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (!code || code->index >= SIZE_DEFAULT_FFMT) + return -EINVAL; + + code->code = m5mols_default_ffmt[code->index].code; + + return 0; +} + +static struct v4l2_subdev_pad_ops m5mols_pad_ops = { + .enum_mbus_code = m5mols_enum_mbus_code, + .get_fmt = m5mols_get_fmt, + .set_fmt = m5mols_set_fmt, +}; + +/* + * m5mols_sync_controls() - apply default scene mode and the current controls + * + * This is used only streaming for syncing between v4l2_ctrl framework and + * m5mols's controls. First, do the scenemode to the sensor, then call + * v4l2_ctrl_handler_setup(). It can be same between some commands and + * the scenemode's in the default v4l2_ctrls. But, such commands of control + * should be prior to the scenemode's one. + */ +int m5mols_sync_controls(struct m5mols_info *info) +{ + int ret = -EINVAL; + + if (!is_ctrl_synced(info)) { + ret = m5mols_change_scenemode(info, REG_SCENE_NORMAL); + if (ret) + return ret; + + v4l2_ctrl_handler_setup(&info->handle); + + info->ctrl_sync = true; + } + + return ret; +} + +/* + * m5mols_start_monitor() - start the monitor mode + * + * Before applying the controls setup the resolution and frame rate + * in PARAMETER mode, and then switch over to MONITOR mode. + */ +static int m5mols_start_monitor(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + int ret; + + ret = m5mols_change_mode(info, REG_MODE_PARAM); + if (!ret) + ret = i2c_w8_param(sd, CAT1_MONITOR_SIZE, info->resolution); + if (!ret) + ret = i2c_w8_param(sd, CAT1_MONITOR_FPS, REG_FPS_30); + if (!ret) + ret = m5mols_change_mode(info, REG_MODE_MONITOR); + if (!ret) + ret = m5mols_sync_controls(info); + + return ret; +} + +static int m5mols_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct m5mols_info *info = to_m5mols(sd); + + if (enable) { + int ret = -EINVAL; + if (is_code(info->code, M5MOLS_RESTYPE_MONITOR)) + ret = m5mols_start_monitor(info); + if (is_code(info->code, M5MOLS_RESTYPE_CAPTURE)) + ret = m5mols_start_capture(info); + + return ret; + } + + return m5mols_change_mode(info, REG_MODE_PARAM); +} + +static const struct v4l2_subdev_video_ops m5mols_video_ops = { + .s_stream = m5mols_s_stream, +}; + +static int m5mols_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int ret; + + info->mode_save = info->mode; + + ret = m5mols_change_mode(info, REG_MODE_PARAM); + if (!ret) + ret = m5mols_set_ctrl(ctrl); + if (!ret) + ret = m5mols_change_mode(info, info->mode_save); + + return ret; +} + +static const struct v4l2_ctrl_ops m5mols_ctrl_ops = { + .s_ctrl = m5mols_s_ctrl, +}; + +static int m5mols_sensor_power(struct m5mols_info *info, bool enable) +{ + struct v4l2_subdev *sd = &info->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct m5mols_platform_data *pdata = info->pdata; + int ret; + + if (enable) { + if (is_powered(info)) + return 0; + + if (info->set_power) { + ret = info->set_power(&client->dev, 1); + if (ret) + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + if (ret) { + info->set_power(&client->dev, 0); + return ret; + } + + gpio_set_value(pdata->gpio_reset, !pdata->reset_polarity); + usleep_range(1000, 1000); + info->power = true; + + return ret; + } + + if (!is_powered(info)) + return 0; + + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + if (ret) + return ret; + + if (info->set_power) + info->set_power(&client->dev, 0); + + gpio_set_value(pdata->gpio_reset, pdata->reset_polarity); + usleep_range(1000, 1000); + info->power = false; + + return ret; +} + +/* m5mols_update_fw() - m5mols_update_fw() is optional. */ +int __attribute__ ((weak)) m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)) +{ + return 0; +} + +/* + * m5mols_sensor_armboot() - booting M-5MOLS internal ARM core. + * + * Booting internal ARM core makes the M-5MOLS is ready for getting commands + * with I2C. It's the first thing to be done after it powered up. It must wait + * at least 520ms recommended by M-5MOLS datasheet, after executing arm booting. + */ +static int m5mols_sensor_armboot(struct v4l2_subdev *sd) +{ + int ret; + + ret = i2c_w8_flash(sd, CATF_CAM_START, REG_START_ARM_BOOT); + if (ret < 0) + return ret; + + msleep(520); + + ret = m5mols_get_version(sd); + if (!ret) + ret = m5mols_update_fw(sd, m5mols_sensor_power); + if (ret) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, "Success ARM Booting\n"); + + ret = i2c_w8_param(sd, CAT1_DATA_INTERFACE, REG_INTERFACE_MIPI); + if (!ret) + ret = m5mols_enable_interrupt(sd, REG_INT_AF); + + return ret; +} + +static int m5mols_init_controls(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 max_exposure, step_zoom; + int ret; + + /* Determine value's range & step of controls for various FW version */ + ret = i2c_r16_ae(sd, CAT3_MAX_GAIN_MON, (u32 *)&max_exposure); + if (!ret) + step_zoom = is_manufacturer(info, SAMSUNG_OPTICS) ? 31 : 1; + if (ret) + return ret; + + v4l2_ctrl_handler_init(&info->handle, 6); + info->autowb = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 0); + info->saturation = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_SATURATION, + 1, 5, 1, 3); + info->zoom = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_ZOOM_ABSOLUTE, + 1, 70, step_zoom, 1); + info->exposure = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE, + 0, max_exposure, 1, (int)max_exposure/2); + info->colorfx = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_COLORFX, + 4, (1 << V4L2_COLORFX_BW), V4L2_COLORFX_NONE); + info->autoexposure = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, + 1, 0, V4L2_EXPOSURE_MANUAL); + + sd->ctrl_handler = &info->handle; + if (info->handle.error) { + dev_err(&client->dev, "Failed to init controls, %d\n", ret); + v4l2_ctrl_handler_free(&info->handle); + return info->handle.error; + } + + v4l2_ctrl_cluster(2, &info->autoexposure); + + return 0; +} + +/* + * m5mols_s_power() - callback function of s_power in the subdev core operation + * + * Breaking the lens is considered when the sensor going to power off. + * But it provided only in the M-5MOLS with Samsung Techwin firiware. And, It + * calls Soft-Landing algorithm. + */ +static int m5mols_s_power(struct v4l2_subdev *sd, int on) +{ + struct m5mols_info *info = to_m5mols(sd); + int ret; + + if (on) { + ret = m5mols_sensor_power(info, true); + if (!ret) + ret = m5mols_sensor_armboot(sd); + if (!ret) + ret = m5mols_init_controls(info); + if (!ret) { + info->ffmt[M5MOLS_RESTYPE_MONITOR] = + m5mols_default_ffmt[M5MOLS_RESTYPE_MONITOR]; + info->ffmt[M5MOLS_RESTYPE_CAPTURE] = + m5mols_default_ffmt[M5MOLS_RESTYPE_CAPTURE]; + } + + return ret; + } + + if (is_manufacturer(info, SAMSUNG_TECHWIN)) { + ret = m5mols_change_mode(info, REG_MODE_MONITOR); + if (!ret) + ret = i2c_w8_lens(sd, CATA_AF_EXECUTE, REG_AF_STOP); + if (!ret) + ret = i2c_w8_lens(sd, CATA_AF_MODE, + REG_AF_MODE_POWEROFF); + if (!ret) + ret = m5mols_check_busy(sd, CAT_SYSTEM, CAT0_STATUS, + REG_AF_IDLE); + if (!ret) + v4l2_info(sd, "Success soft-landing lens\n"); + } + + ret = m5mols_sensor_power(info, false); + if (!ret) { + v4l2_ctrl_handler_free(&info->handle); + info->ctrl_sync = false; + } + + return ret; +} + +static int m5mols_log_status(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + + v4l2_ctrl_handler_log_status(&info->handle, sd->name); + + return 0; +} + +static const struct v4l2_subdev_core_ops m5mols_core_ops = { + .s_power = m5mols_s_power, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .log_status = m5mols_log_status, +}; + +static const struct v4l2_subdev_ops m5mols_ops = { + .core = &m5mols_core_ops, + .pad = &m5mols_pad_ops, + .video = &m5mols_video_ops, +}; + +static void m5mols_irq_work(struct work_struct *work) +{ + struct m5mols_info *info = container_of(work, struct m5mols_info, + work_irq); + struct v4l2_subdev *sd = &info->sd; + u32 reg; + int ret; + + /* + * For preventing I2C operation when it's not powered and prepared for + * the I2C operation. + */ + if (!is_powered(info) || + i2c_r8_system(sd, CAT0_INT_FACTOR, &info->interrupt)) + return; + + switch (info->interrupt & REG_INT_MASK) { + case REG_INT_AF: + if (info->ver.af) { + ret = i2c_r8_lens(sd, CATA_AF_STATUS, ®); + v4l2_dbg(2, m5mols_debug, sd, "= AF %s\n", + reg == REG_AF_FAIL ? "Failed" : + reg == REG_AF_SUCCESS ? "Success" : + reg == REG_AF_IDLE ? "Idle" : "Busy"); + } + break; + case REG_INT_CAPTURE: + if (!is_captured(info)) { + wake_up_interruptible(&info->wait_capture); + info->capture = true; + } + v4l2_dbg(2, m5mols_debug, sd, "= CAPTURE\n"); + break; + default: + v4l2_dbg(2, m5mols_debug, sd, "= Nothing : %02x\n", reg); + break; + }; +} + +static irqreturn_t m5mols_irq_handler(int irq, void *data) +{ + struct v4l2_subdev *sd = data; + struct m5mols_info *info = to_m5mols(sd); + + schedule_work(&info->work_irq); + + return IRQ_HANDLED; +} + +static int m5mols_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct m5mols_platform_data *pdata = + client->dev.platform_data; + struct m5mols_info *info; + struct v4l2_subdev *sd; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!gpio_is_valid(pdata->gpio_reset)) { + dev_err(&client->dev, "No valid RESET GPIO specified\n"); + return -EINVAL; + } + + if (!pdata->irq) { + dev_err(&client->dev, "Interrupt not assigned\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct m5mols_info), GFP_KERNEL); + if (info == NULL) { + dev_err(&client->dev, "Failed to allocate info\n"); + return -ENOMEM; + } + + info->pdata = pdata; + info->set_power = pdata->set_power; + + ret = gpio_request(pdata->gpio_reset, "M5MOLS_NRST"); + if (ret) { + dev_err(&client->dev, "Failed to request gpio: %d\n", ret); + goto out_free; + } + + gpio_direction_output(pdata->gpio_reset, pdata->reset_polarity); + + ret = regulator_bulk_get(&client->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators: %d\n", ret); + goto out_gpio; + } + + sd = &info->sd; + strlcpy(sd->name, MOD_NAME, sizeof(sd->name)); + v4l2_i2c_subdev_init(sd, client, &m5mols_ops); + + info->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&sd->entity, 1, &info->pad, 0); + if (ret < 0) + goto out_reg; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + + init_waitqueue_head(&info->wait_capture); + INIT_WORK(&info->work_irq, m5mols_irq_work); + ret = request_irq(pdata->irq, m5mols_irq_handler, + IRQF_TRIGGER_RISING, MOD_NAME, sd); + if (ret) { + dev_err(&client->dev, "Failed to request irq: %d\n", ret); + goto out_me; + } + info->curr_res_type = M5MOLS_RESTYPE_MONITOR; + + v4l2_dbg(1, m5mols_debug, sd, "Probed m5mols driver.\n"); + + return 0; +out_me: + media_entity_cleanup(&sd->entity); +out_reg: + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); +out_gpio: + gpio_free(pdata->gpio_reset); +out_free: + kfree(info); + return ret; +} + +static int m5mols_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct m5mols_info *info = to_m5mols(sd); + + v4l2_device_unregister_subdev(sd); + free_irq(info->pdata->irq, sd); + + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + gpio_free(info->pdata->gpio_reset); + media_entity_cleanup(&sd->entity); + kfree(info); + + v4l2_dbg(1, m5mols_debug, sd, "Removed m5mols driver\n"); + + return 0; +} + +static const struct i2c_device_id m5mols_id[] = { + { MOD_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, m5mols_id); + +static struct i2c_driver m5mols_i2c_driver = { + .driver = { + .name = MOD_NAME, + }, + .probe = m5mols_probe, + .remove = m5mols_remove, + .id_table = m5mols_id, +}; + +static int __init m5mols_mod_init(void) +{ + return i2c_add_driver(&m5mols_i2c_driver); +} + +static void __exit m5mols_mod_exit(void) +{ + i2c_del_driver(&m5mols_i2c_driver); +} + +module_init(m5mols_mod_init); +module_exit(m5mols_mod_exit); + +MODULE_AUTHOR("HeungJun Kim <riverful.kim@xxxxxxxxxxx>"); +MODULE_AUTHOR("Dongsoo Kim <dongsoo45.kim@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Fujitsu M-5MOLS 8M Pixel camera driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/m5mols/m5mols_reg.h b/drivers/media/video/m5mols/m5mols_reg.h new file mode 100644 index 0000000..f07b362 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_reg.h @@ -0,0 +1,321 @@ +/* + * Register map for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@xxxxxxxxxxx + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@xxxxxxxxxxx + * + * 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. + */ + +#ifndef M5MOLS_REG_H +#define M5MOLS_REG_H + +/* + * Category section register + * + * The category means set including relevant command of M-5MOLS. + */ +#define CAT_SYSTEM 0x00 +#define CAT_PARAM 0x01 +#define CAT_MONITOR 0x02 +#define CAT_AE 0x03 +#define CAT_WB 0x06 +#define CAT_EXIF 0x07 +#define CAT_FD 0x09 +#define CAT_LENS 0x0a +#define CAT_CAPTURE_PARAMETER 0x0b +#define CAT_CAPTURE_CONTROL 0x0c +#define CAT_FLASH 0x0f /* related with FW, Verions, booting */ + +/* + * Category 0 - SYSTEM mode + * + * The SYSTEM mode in the M-5MOLS means area available to handle with the whole + * & all-round system of sensor. It deals with version/interrupt/setting mode & + * even sensor's status. Especially, the M-5MOLS sensor with ISP varys by + * packaging & manufactur vendors, even the customer user vendor. And the + * detailed function may have a difference. The version information can + * determine what method use in the driver. + * + * There is many registers between customer version address and awb one. For + * more specific contents, reference the m5mols.h. + */ +#define CAT0_CUSTOMER_CODE 0x00 /* customer version*/ +#define CAT0_VERSION_AWB 0x09 /* Auto WB version */ +#define CAT0_VERSION_STRING 0x0a /* string including M-5MOLS */ +#define CAT0_SYSMODE 0x0b /* SYSTEM mode register */ +#define CAT0_STATUS 0x0c /* SYSTEM mode status register */ +#define CAT0_INT_FACTOR 0x10 /* interrupt pending register */ +#define CAT0_INT_ENABLE 0x11 /* interrupt enable register */ + +#define REG_MODE_SYSINIT 0x00 /* SYSTEM mode */ +#define REG_MODE_PARAM 0x01 /* PARAMETER mode */ +#define REG_MODE_MONITOR 0x02 /* MONITOR mode */ +#define REG_MODE_CAPTURE 0x03 /* CAPTURE mode */ + +#define REG_INT_MODE (1 << 0) +#define REG_INT_AF (1 << 1) +#define REG_INT_ZOOM (1 << 2) +#define REG_INT_CAPTURE (1 << 3) +#define REG_INT_FRAMESYNC (1 << 4) +#define REG_INT_FD (1 << 5) +#define REG_INT_LENS_INIT (1 << 6) +#define REG_INT_SOUND (1 << 7) +#define REG_INT_MASK 0x0f + +/* + * category 1 - PARAMETER mode + * + * This category supports function of camera features of M-5MOLS. It means we + * can handle with preview(MONITOR) resolution size/frame per second/interface + * between the sensor and the Application Processor/even the image effect. + */ +#define CAT1_DATA_INTERFACE 0x00 /* interface between sensor and AP */ +#define CAT1_MONITOR_SIZE 0x01 /* resolution at the MONITOR mode */ +#define CAT1_MONITOR_FPS 0x02 /* frame per second at this mode */ +#define CAT1_EFFECT 0x0b /* image effects */ + +#define REG_INTERFACE_MIPI 0x02 + +#define REG_FPS_30 0x02 + +#define REG_EFFECT_OFF 0x00 +#define REG_EFFECT_NEGA 0x01 +#define REG_EFFECT_EMBOSS 0x06 +#define REG_EFFECT_OUTLINE 0x07 +#define REG_EFFECT_WATERCOLOR 0x08 + +/* + * Category 2 - MONITOR mode + * + * The MONITOR mode is same as preview mode as we said. The M-5MOLS has another + * mode named "Preview", but this preview mode is used at the case specific + * vider-recording mode. This mmode supports only YUYV format. On the other + * hand, the JPEG & RAW formats is supports by CAPTURE mode. And, there are + * another options like zoom/color effect(different with effect in PARAMETER + * mode)/anti hand shaking algorithm. + */ +#define CAT2_ZOOM 0x01 /* set the zoom position & execute */ +#define CAT2_ZOOM_STEP 0x03 /* set the zoom step */ +#define CAT2_CFIXB 0x09 /* CB value for color effect */ +#define CAT2_CFIXR 0x0a /* CR value for color effect */ +#define CAT2_COLOR_EFFECT 0x0b /* set on/off of color effect */ +#define CAT2_CHROMA_LVL 0x0f /* set chroma level */ +#define CAT2_CHROMA_EN 0x10 /* set on/off of choroma */ +#define CAT2_EDGE_LVL 0x11 /* set sharpness level */ +#define CAT2_EDGE_EN 0x12 /* set on/off sharpness */ +#define CAT2_TONE_CTL 0x25 /* set tone color(contrast) */ + +#define REG_CFIXB_SEPIA 0xd8 +#define REG_CFIXR_SEPIA 0x18 + +#define REG_COLOR_EFFECT_OFF 0x00 +#define REG_COLOR_EFFECT_ON 0x01 + +#define REG_CHROMA_OFF 0x00 +#define REG_CHROMA_ON 0x01 + +#define REG_EDGE_OFF 0x00 +#define REG_EDGE_ON 0x01 + +/* + * Category 3 - Auto Exposure + * + * The M-5MOLS exposure capbility is detailed as which is similar to digital + * camera. This category supports AE locking/various AE mode(range of exposure) + * /ISO/flickering/EV bias/shutter/meteoring, and anything else. And the + * maximum/minimum exposure gain value depending on M-5MOLS firmware, may be + * different. So, this category also provide getting the max/min values. And, + * each MONITOR and CAPTURE mode has each gain/shutter/max exposure values. + */ +#define CAT3_AE_LOCK 0x00 /* locking Auto exposure */ +#define CAT3_AE_MODE 0x01 /* set AE mode, mode means range */ +#define CAT3_ISO 0x05 /* set ISO */ +#define CAT3_EV_PRESET_MODE_MON 0x0a /* EV(scenemode) preset for MONITOR */ +#define CAT3_EV_PRESET_MODE_CAP 0x0b /* EV(scenemode) preset for CAPTURE */ +#define CAT3_MANUAL_GAIN_MON 0x12 /* meteoring value for the MONITOR */ +#define CAT3_MAX_GAIN_MON 0x1a /* max gain value for the MONITOR */ +#define CAT3_MANUAL_GAIN_CAP 0x26 /* meteoring value for the CAPTURE */ +#define CAT3_AE_INDEX 0x38 /* AE index */ + +#define REG_AE_UNLOCK 0x00 +#define REG_AE_LOCK 0x01 + +#define REG_AE_MODE_OFF 0x00 /* AE off */ +#define REG_AE_MODE_ALL 0x01 /* calc AE in all block integral */ +#define REG_AE_MODE_CENTER 0x03 /* calc AE in center weighted */ +#define REG_AE_MODE_SPOT 0x06 /* calc AE in specific spot */ + +#define REG_ISO_AUTO 0x00 +#define REG_ISO_50 0x0 +#define REG_ISO_100 0x02 +#define REG_ISO_200 0x03 +#define REG_ISO_400 0x04 +#define REG_ISO_800 0x05 + +#define REG_SCENE_NORMAL 0x00 +#define REG_SCENE_PORTRAIT 0x01 +#define REG_SCENE_LANDSCAPE 0x02 +#define REG_SCENE_SPORTS 0x03 +#define REG_SCENE_PARTY_INDOOR 0x04 +#define REG_SCENE_BEACH_SNOW 0x05 +#define REG_SCENE_SUNSET 0x06 +#define REG_SCENE_DAWN_DUSK 0x07 +#define REG_SCENE_FALL 0x08 +#define REG_SCENE_NIGHT 0x09 +#define REG_SCENE_AGAINST_LIGHT 0x0a +#define REG_SCENE_FIRE 0x0b +#define REG_SCENE_TEXT 0x0c +#define REG_SCENE_CANDLE 0x0d +#define REG_SCENE_NONE 0xff + +#define REG_AE_INDEX_20_NEG 0x00 +#define REG_AE_INDEX_15_NEG 0x01 +#define REG_AE_INDEX_10_NEG 0x02 +#define REG_AE_INDEX_05_NEG 0x03 +#define REG_AE_INDEX_00 0x04 +#define REG_AE_INDEX_05_POS 0x05 +#define REG_AE_INDEX_10_POS 0x06 +#define REG_AE_INDEX_15_POS 0x07 +#define REG_AE_INDEX_20_POS 0x08 + +/* + * Category 6 - White Balance + * + * This cagetory provide AWB locking/mode/preset/speed/gain bias, etc. + */ +#define CAT6_AWB_LOCK 0x00 /* locking Auto Whitebalance */ +#define CAT6_AWB_MODE 0x02 /* set Auto or Manual */ +#define CAT6_AWB_MANUAL 0x03 /* set Manual(preset) value */ + +#define REG_AWB_UNLOCK 0x00 +#define REG_AWB_LOCK 0x01 + +#define REG_AWB_MODE_AUTO 0x01 /* AWB off */ +#define REG_AWB_MODE_PRESET 0x02 /* AWB preset */ + +#define REG_AWB_INCANDESCENT 0x01 +#define REG_AWB_FLUORESCENT_1 0x02 +#define REG_AWB_FLUORESCENT_2 0x03 +#define REG_AWB_DAYLIGHT 0x04 +#define REG_AWB_CLOUDY 0x05 +#define REG_AWB_SHADE 0x06 +#define REG_AWB_HORIZON 0x07 +#define REG_AWB_LEDLIGHT 0x09 + +/* + * Category 7 - EXIF information + */ +#define CAT7_INFO_EXPTIME_NU 0x00 +#define CAT7_INFO_EXPTIME_DE 0x04 +#define CAT7_INFO_TV_NU 0x08 +#define CAT7_INFO_TV_DE 0x0c +#define CAT7_INFO_AV_NU 0x10 +#define CAT7_INFO_AV_DE 0x14 +#define CAT7_INFO_BV_NU 0x18 +#define CAT7_INFO_BV_DE 0x1c +#define CAT7_INFO_EBV_NU 0x20 +#define CAT7_INFO_EBV_DE 0x24 +#define CAT7_INFO_ISO 0x28 +#define CAT7_INFO_FLASH 0x2a +#define CAT7_INFO_SDR 0x2c +#define CAT7_INFO_QVAL 0x2e + +/* + * Category 9 - Face Detection + */ +#define CAT9_FD_CTL 0x00 + +#define BIT_FD_EN 0 +#define BIT_FD_DRAW_FACE_FRAME 4 +#define BIT_FD_DRAW_SMILE_LVL 6 +#define REG_FD(shift) (1 << shift) +#define REG_FD_OFF 0x0 + +/* + * Category A - Lens Parameter + */ +#define CATA_AF_MODE 0x01 +#define CATA_AF_EXECUTE 0x02 +#define CATA_AF_STATUS 0x03 +#define CATA_AF_VERSION 0x0a + +#define REG_AF_FAIL 0x00 +#define REG_AF_SUCCESS 0x02 +#define REG_AF_IDLE 0x04 +#define REG_AF_BUSY 0x05 + +#define REG_AF_MODE_NORMAL 0x00 /* Normal AF, one time */ +#define REG_AF_MODE_MACRO 0x01 /* Macro AF, one time */ +#define REG_AF_MODE_POWEROFF 0x07 + +#define REG_AF_STOP 0x00 +#define REG_AF_EXE_AUTO 0x01 +#define REG_AF_EXE_CAF 0x02 + +/* + * Category B - CAPTURE Parameter + */ +#define CATB_YUVOUT_MAIN 0x00 +#define CATB_MAIN_IMAGE_SIZE 0x01 +#define CATB_MCC_MODE 0x1d +#define CATB_WDR_EN 0x2c +#define CATB_LIGHT_CTRL 0x40 +#define CATB_FLASH_CTRL 0x41 + +/* n = 0..3 */ +#define CATB_JPEG_SIZE_MAX(n) (0x12 - (n)) +#define CATB_JPEG_SIZE_MIN(n) (0x16 - (n)) + +#define REG_YUV422 0x00 +#define REG_BAYER10 0x05 +#define REG_BAYER8 0x06 +#define REG_JPEG 0x10 + +#define REG_MCC_OFF 0x00 +#define REG_MCC_NORMAL 0x01 + +#define REG_WDR_OFF 0x00 +#define REG_WDR_ON 0x01 +#define REG_WDR_AUTO 0x02 + +#define REG_LIGHT_OFF 0x00 +#define REG_LIGHT_ON 0x01 +#define REG_LIGHT_AUTO 0x02 + +#define REG_FLASH_OFF 0x00 +#define REG_FLASH_ON 0x01 +#define REG_FLASH_AUTO 0x02 + +/* + * Category C - CAPTURE Control + */ +#define CATC_CAP_MODE 0x00 +#define CATC_CAP_SEL_FRAME 0x06 /* It determines Single or Multi. */ +#define CATC_CAP_START 0x09 +#define CATC_CAP_IMAGE_SIZE 0x0d +#define CATC_CAP_THUMB_SIZE 0x11 + +#define REG_CAP_MODE_NONE 0x00 +#define REG_CAP_MODE_ANTI_SHAKE 0x02 + +#define REG_CAP_START_MAIN 0x01 +#define REG_CAP_START_THUMB 0x03 + +/* + * Category F - Flash + * + * This mode provides functions about internal flash stuff and system startup. + */ +#define CATF_CAM_START 0x12 /* It start internal ARM core booting + * after power-up */ + +#define REG_START_ARM_BOOT 0x01 + +#endif /* M5MOLS_REG_H */ diff --git a/include/media/m5mols.h b/include/media/m5mols.h new file mode 100644 index 0000000..2d7e7ca --- /dev/null +++ b/include/media/m5mols.h @@ -0,0 +1,35 @@ +/* + * Driver header for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@xxxxxxxxxxx + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@xxxxxxxxxxx + * + * 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. + */ + +#ifndef MEDIA_M5MOLS_H +#define MEDIA_M5MOLS_H + +/** + * struct m5mols_platform_data - platform data for M-5MOLS driver + * @irq: GPIO getting the irq pin of M-5MOLS + * @gpio_reset: GPIO driving the reset pin of M-5MOLS + * @reset_polarity: active state for gpio_rst pin, 0 or 1 + * @set_power: an additional callback to the board setup code + * to be called after enabling and before disabling + * the sensor's supply regulators + */ +struct m5mols_platform_data { + int irq; + int gpio_reset; + u8 reset_polarity; + int (*set_power)(struct device *dev, int on); +}; + +#endif /* MEDIA_M5MOLS_H */ -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html