From c6da2cfeb05178a11c6d062a06f8078150ee492f Mon Sep 17 00:00:00 2001 From: codeworkx Date: Sat, 2 Jun 2012 13:09:29 +0200 Subject: samsung update 1 --- drivers/media/video/s5k4ba.c | 594 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 594 insertions(+) create mode 100644 drivers/media/video/s5k4ba.c (limited to 'drivers/media/video/s5k4ba.c') diff --git a/drivers/media/video/s5k4ba.c b/drivers/media/video/s5k4ba.c new file mode 100644 index 0000000..6c2ccbb --- /dev/null +++ b/drivers/media/video/s5k4ba.c @@ -0,0 +1,594 @@ +/* linux/drivers/media/video/s5k4ba.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Driver for S5K4BA (UXGA camera) from Samsung Electronics + * 1/4" 2.0Mp CMOS Image Sensor SoC with an Embedded Image Processor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_VIDEO_SAMSUNG_V4L2 +#include +#endif + +#include "s5k4ba.h" + +#define S5K4BA_DRIVER_NAME "S5K4BA" + +/* Default resolution & pixelformat. plz ref s5k4ba_platform.h */ +#define DEFAULT_RES WVGA /* Index of resoultion */ +#define DEFAUT_FPS_INDEX S5K4BA_15FPS +#define DEFAULT_FMT V4L2_PIX_FMT_UYVY /* YUV422 */ + +/* + * Specification + * Parallel : ITU-R. 656/601 YUV422, RGB565, RGB888 (Up to VGA), RAW10 + * Serial : MIPI CSI2 (single lane) YUV422, RGB565, RGB888 (Up to VGA), RAW10 + * Resolution : 1280 (H) x 1024 (V) + * Image control : Brightness, Contrast, Saturation, Sharpness, Glamour + * Effect : Mono, Negative, Sepia, Aqua, Sketch + * FPS : 15fps @full resolution, 30fps @VGA, 24fps @720p + * Max. pixel clock frequency : 48MHz(upto) + * Internal PLL (6MHz to 27MHz input frequency) + */ + +/* Camera functional setting values configured by user concept */ +struct s5k4ba_userset { + signed int exposure_bias; /* V4L2_CID_EXPOSURE */ + unsigned int ae_lock; + unsigned int awb_lock; + unsigned int auto_wb; /* V4L2_CID_AUTO_WHITE_BALANCE */ + unsigned int manual_wb; /* V4L2_CID_WHITE_BALANCE_PRESET */ + unsigned int wb_temp; /* V4L2_CID_WHITE_BALANCE_TEMPERATURE */ + unsigned int effect; /* Color FX (AKA Color tone) */ + unsigned int contrast; /* V4L2_CID_CONTRAST */ + unsigned int saturation; /* V4L2_CID_SATURATION */ + unsigned int sharpness; /* V4L2_CID_SHARPNESS */ + unsigned int glamour; +}; + +struct s5k4ba_state { + struct s5k4ba_platform_data *pdata; + struct v4l2_subdev sd; + struct v4l2_pix_format pix; + struct v4l2_fract timeperframe; + struct s5k4ba_userset userset; + int freq; /* MCLK in KHz */ + int is_mipi; + int isize; + int ver; + int fps; +}; + +static inline struct s5k4ba_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s5k4ba_state, sd); +} + +/* + * S5K4BA register structure : 2bytes address, 2bytes value + * retry on write failure up-to 5 times + */ +static inline int s5k4ba_write(struct v4l2_subdev *sd, u8 addr, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct i2c_msg msg[1]; + unsigned char reg[2]; + int err = 0; + int retry = 0; + + + if (!client->adapter) + return -ENODEV; + +again: + msg->addr = client->addr; + msg->flags = 0; + msg->len = 2; + msg->buf = reg; + + reg[0] = addr & 0xff; + reg[1] = val & 0xff; + + err = i2c_transfer(client->adapter, msg, 1); + if (err >= 0) + return err; /* Returns here on success */ + + /* abnormal case: retry 5 times */ + if (retry < 5) { + dev_err(&client->dev, "%s: address: 0x%02x%02x, " \ + "value: 0x%02x%02x\n", __func__, \ + reg[0], reg[1], reg[2], reg[3]); + retry++; + goto again; + } + + return err; +} + +static int s5k4ba_i2c_write(struct v4l2_subdev *sd, unsigned char i2c_data[], + unsigned char length) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned char buf[length], i; + struct i2c_msg msg = {client->addr, 0, length, buf}; + + for (i = 0; i < length; i++) + buf[i] = i2c_data[i]; + + return i2c_transfer(client->adapter, &msg, 1) == 1 ? 0 : -EIO; +} + +static int s5k4ba_write_regs(struct v4l2_subdev *sd, unsigned char regs[], + int size) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int i, err; + + for (i = 0; i < size; i++) { + err = s5k4ba_i2c_write(sd, ®s[i], sizeof(regs[i])); + if (err < 0) + v4l_info(client, "%s: register set failed\n", \ + __func__); + } + + return 0; /* FIXME */ +} + +static const char *s5k4ba_querymenu_wb_preset[] = { + "WB Tungsten", "WB Fluorescent", "WB sunny", "WB cloudy", NULL +}; + +static const char *s5k4ba_querymenu_effect_mode[] = { + "Effect Sepia", "Effect Aqua", "Effect Monochrome", + "Effect Negative", "Effect Sketch", NULL +}; + +static const char *s5k4ba_querymenu_ev_bias_mode[] = { + "-3EV", "-2,1/2EV", "-2EV", "-1,1/2EV", + "-1EV", "-1/2EV", "0", "1/2EV", + "1EV", "1,1/2EV", "2EV", "2,1/2EV", + "3EV", NULL +}; + +static struct v4l2_queryctrl s5k4ba_controls[] = { + { + /* + * For now, we just support in preset type + * to be close to generic WB system, + * we define color temp range for each preset + */ + .id = V4L2_CID_WHITE_BALANCE_TEMPERATURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "White balance in kelvin", + .minimum = 0, + .maximum = 10000, + .step = 1, + .default_value = 0, /* FIXME */ + }, + { + .id = V4L2_CID_WHITE_BALANCE_PRESET, + .type = V4L2_CTRL_TYPE_MENU, + .name = "White balance preset", + .minimum = 0, + .maximum = ARRAY_SIZE(s5k4ba_querymenu_wb_preset) - 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto white balance", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Exposure bias", + .minimum = 0, + .maximum = ARRAY_SIZE(s5k4ba_querymenu_ev_bias_mode) - 2, + .step = 1, + .default_value = \ + (ARRAY_SIZE(s5k4ba_querymenu_ev_bias_mode) - 2) / 2, + /* 0 EV */ + }, + { + .id = V4L2_CID_COLORFX, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Image Effect", + .minimum = 0, + .maximum = ARRAY_SIZE(s5k4ba_querymenu_effect_mode) - 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 4, + .step = 1, + .default_value = 2, + }, + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 4, + .step = 1, + .default_value = 2, + }, + { + .id = V4L2_CID_SHARPNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Sharpness", + .minimum = 0, + .maximum = 4, + .step = 1, + .default_value = 2, + }, +}; + +const char * const *s5k4ba_ctrl_get_menu(u32 id) +{ + switch (id) { + case V4L2_CID_WHITE_BALANCE_PRESET: + return s5k4ba_querymenu_wb_preset; + + case V4L2_CID_COLORFX: + return s5k4ba_querymenu_effect_mode; + + case V4L2_CID_EXPOSURE: + return s5k4ba_querymenu_ev_bias_mode; + + default: + return v4l2_ctrl_get_menu(id); + } +} + +static inline struct v4l2_queryctrl const *s5k4ba_find_qctrl(int id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s5k4ba_controls); i++) + if (s5k4ba_controls[i].id == id) + return &s5k4ba_controls[i]; + + return NULL; +} + +static int s5k4ba_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s5k4ba_controls); i++) { + if (s5k4ba_controls[i].id == qc->id) { + memcpy(qc, &s5k4ba_controls[i], \ + sizeof(struct v4l2_queryctrl)); + return 0; + } + } + + return -EINVAL; +} + +static int s5k4ba_querymenu(struct v4l2_subdev *sd, struct v4l2_querymenu *qm) +{ + struct v4l2_queryctrl qctrl; + + qctrl.id = qm->id; + s5k4ba_queryctrl(sd, &qctrl); + + return v4l2_ctrl_query_menu(qm, &qctrl, s5k4ba_ctrl_get_menu(qm->id)); +} + +/* + * Clock configuration + * Configure expected MCLK from host and return EINVAL if not supported clock + * frequency is expected + * freq : in Hz + * flag : not supported for now + */ +static int s5k4ba_s_crystal_freq(struct v4l2_subdev *sd, u32 freq, u32 flags) +{ + int err = -EINVAL; + + return err; +} + +static int s5k4ba_enum_framesizes(struct v4l2_subdev *sd, \ + struct v4l2_frmsizeenum *fsize) +{ + int err = 0; + + return err; +} + +static int s5k4ba_enum_frameintervals(struct v4l2_subdev *sd, + struct v4l2_frmivalenum *fival) +{ + int err = 0; + + return err; +} + +static int s5k4ba_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *param) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err = 0; + + dev_dbg(&client->dev, "%s\n", __func__); + + return err; +} + +static int s5k4ba_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *param) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err = 0; + + dev_dbg(&client->dev, "%s: numerator %d, denominator: %d\n", \ + __func__, param->parm.capture.timeperframe.numerator, \ + param->parm.capture.timeperframe.denominator); + + return err; +} + +static int s5k4ba_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct s5k4ba_state *state = to_state(sd); + struct s5k4ba_userset userset = state->userset; + int err = -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ctrl->value = userset.exposure_bias; + err = 0; + break; + + case V4L2_CID_AUTO_WHITE_BALANCE: + ctrl->value = userset.auto_wb; + err = 0; + break; + + case V4L2_CID_WHITE_BALANCE_PRESET: + ctrl->value = userset.manual_wb; + err = 0; + break; + + case V4L2_CID_COLORFX: + ctrl->value = userset.effect; + err = 0; + break; + + case V4L2_CID_CONTRAST: + ctrl->value = userset.contrast; + err = 0; + break; + + case V4L2_CID_SATURATION: + ctrl->value = userset.saturation; + err = 0; + break; + + case V4L2_CID_SHARPNESS: + ctrl->value = userset.saturation; + err = 0; + break; + + default: + dev_err(&client->dev, "%s: no such ctrl\n", __func__); + break; + } + + return err; +} + +static int s5k4ba_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ +#ifdef S5K4BA_COMPLETE + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err = -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + dev_dbg(&client->dev, "%s: V4L2_CID_EXPOSURE\n", __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_ev_bias[ctrl->value], \ + sizeof(s5k4ba_regs_ev_bias[ctrl->value])); + break; + + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&client->dev, "%s: V4L2_CID_AUTO_WHITE_BALANCE\n", \ + __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_awb_enable[ctrl->value], \ + sizeof(s5k4ba_regs_awb_enable[ctrl->value])); + break; + + case V4L2_CID_WHITE_BALANCE_PRESET: + dev_dbg(&client->dev, "%s: V4L2_CID_WHITE_BALANCE_PRESET\n", \ + __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_wb_preset[ctrl->value], \ + sizeof(s5k4ba_regs_wb_preset[ctrl->value])); + break; + + case V4L2_CID_COLORFX: + dev_dbg(&client->dev, "%s: V4L2_CID_COLORFX\n", __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_color_effect[ctrl->value], \ + sizeof(s5k4ba_regs_color_effect[ctrl->value])); + break; + + case V4L2_CID_CONTRAST: + dev_dbg(&client->dev, "%s: V4L2_CID_CONTRAST\n", __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_contrast_bias[ctrl->value], \ + sizeof(s5k4ba_regs_contrast_bias[ctrl->value])); + break; + + case V4L2_CID_SATURATION: + dev_dbg(&client->dev, "%s: V4L2_CID_SATURATION\n", __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_saturation_bias[ctrl->value], \ + sizeof(s5k4ba_regs_saturation_bias[ctrl->value])); + break; + + case V4L2_CID_SHARPNESS: + dev_dbg(&client->dev, "%s: V4L2_CID_SHARPNESS\n", __func__); + err = s5k4ba_write_regs(sd, \ + (unsigned char *) s5k4ba_regs_sharpness_bias[ctrl->value], \ + sizeof(s5k4ba_regs_sharpness_bias[ctrl->value])); + break; + + default: + dev_err(&client->dev, "%s: no such control\n", __func__); + break; + } + + if (err < 0) + goto out; + else + return 0; + +out: + dev_dbg(&client->dev, "%s: vidioc_s_ctrl failed\n", __func__); + return err; +#else + return 0; +#endif +} + +static int s5k4ba_init(struct v4l2_subdev *sd, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err = -EINVAL, i; + + v4l_info(client, "%s: camera initialization start\n", __func__); + + for (i = 0; i < S5K4BA_INIT_REGS; i++) { + err = s5k4ba_i2c_write(sd, s5k4ba_init_reg[i], \ + sizeof(s5k4ba_init_reg[i])); + if (err < 0) + v4l_info(client, "%s: register set failed\n", \ + __func__); + } + if (err < 0) { + v4l_err(client, "%s: camera initialization failed\n", \ + __func__); + return -EIO; /* FIXME */ + } + + return 0; +} + +static int s5k4ba_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *ffmt) +{ + return 0; +} + +static const struct v4l2_subdev_core_ops s5k4ba_core_ops = { + .init = s5k4ba_init, /* initializing API */ + .queryctrl = s5k4ba_queryctrl, + .querymenu = s5k4ba_querymenu, + .g_ctrl = s5k4ba_g_ctrl, + .s_ctrl = s5k4ba_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops s5k4ba_video_ops = { + .s_crystal_freq = s5k4ba_s_crystal_freq, + .enum_framesizes = s5k4ba_enum_framesizes, + .enum_frameintervals = s5k4ba_enum_frameintervals, + .s_mbus_fmt = s5k4ba_s_fmt, + .g_parm = s5k4ba_g_parm, + .s_parm = s5k4ba_s_parm, +}; + +static const struct v4l2_subdev_ops s5k4ba_ops = { + .core = &s5k4ba_core_ops, + .video = &s5k4ba_video_ops, +}; + +/* + * s5k4ba_probe + * Fetching platform data is being done with s_config subdev call. + * In probe routine, we just register subdev device + */ +static int s5k4ba_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct s5k4ba_state *state; + struct v4l2_subdev *sd; + + state = kzalloc(sizeof(struct s5k4ba_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + sd = &state->sd; + strcpy(sd->name, S5K4BA_DRIVER_NAME); + + /* Registering subdev */ + v4l2_i2c_subdev_init(sd, client, &s5k4ba_ops); + printk("%s\n", __func__); + dev_info(&client->dev, "s5k4ba has been probed\n"); + return 0; +} + + +static int s5k4ba_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(to_state(sd)); + return 0; +} + +static const struct i2c_device_id s5k4ba_id[] = { + { S5K4BA_DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, s5k4ba_id); + +static struct i2c_driver s5k4ba_i2c_driver = { + .driver = { + .name = S5K4BA_DRIVER_NAME, + }, + .probe = s5k4ba_probe, + .remove = s5k4ba_remove, + .id_table = s5k4ba_id, +}; + +static int __init s5k4ba_mod_init(void) +{ + return i2c_add_driver(&s5k4ba_i2c_driver); +} + +static void __exit s5k4ba_mod_exit(void) +{ + i2c_del_driver(&s5k4ba_i2c_driver); +} +module_init(s5k4ba_mod_init); +module_exit(s5k4ba_mod_exit); + +MODULE_DESCRIPTION("Samsung Electronics S5K4BA UXGA camera driver"); +MODULE_AUTHOR("Jinsung Yang "); +MODULE_LICENSE("GPL"); -- cgit v1.1