/* 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");