| /* |
| * Galaxycore GC0313 sensor driver |
| * |
| * 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 version 2. |
| * |
| * This program is distributed "as is" WITHOUT ANY WARRANTY of any |
| *kind, whether express or implied; without even the implied warranty |
| *of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/videodev2.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/log2.h> |
| #include <linux/delay.h> |
| |
| #include <media/v4l2-subdev.h> |
| #include <media/v4l2-chip-ident.h> |
| #include <media/soc_camera.h> |
| #include <linux/videodev2_brcm.h> |
| |
| #include <linux/gpio.h> |
| |
| #include "gc0313.h" |
| |
| #define GC0313_ID 0xd0d0 |
| /* GC0313 has only one fixed colorspace per pixelcode */ |
| struct gc0313_datafmt { |
| enum v4l2_mbus_pixelcode code; |
| enum v4l2_colorspace colorspace; |
| }; |
| |
| struct gc0313_timing_cfg { |
| u16 x_addr_start; |
| u16 y_addr_start; |
| u16 x_addr_end; |
| u16 y_addr_end; |
| u16 h_output_size; |
| u16 v_output_size; |
| u16 h_total_size; |
| u16 v_total_size; |
| u16 isp_h_offset; |
| u16 isp_v_offset; |
| u8 h_odd_ss_inc; |
| u8 h_even_ss_inc; |
| u8 v_odd_ss_inc; |
| u8 v_even_ss_inc; |
| u8 out_mode_sel; |
| u8 sclk_dividers; |
| u8 sys_mipi_clk; |
| |
| }; |
| |
| static const struct gc0313_datafmt gc0313_fmts[] = { |
| /* |
| * Order important: first natively supported, |
| *second supported with a GPIO extender |
| */ |
| {V4L2_MBUS_FMT_UYVY8_2X8, V4L2_COLORSPACE_JPEG}, |
| {V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG}, |
| }; |
| |
| enum gc0313_size { |
| GC0313_SIZE_QVGA, |
| GC0313_SIZE_VGA, |
| GC0313_SIZE_LAST, |
| GC0313_SIZE_MAX |
| }; |
| |
| static const struct v4l2_frmsize_discrete gc0313_frmsizes[GC0313_SIZE_LAST] = { |
| {320, 240}, |
| {640, 480}, |
| }; |
| |
| |
| /* Find a data format by a pixel code in an array */ |
| static int gc0313_find_datafmt(enum v4l2_mbus_pixelcode code) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(gc0313_fmts); i++) |
| if (gc0313_fmts[i].code == code) |
| break; |
| |
| /* If not found, select latest */ |
| if (i >= ARRAY_SIZE(gc0313_fmts)) |
| i = ARRAY_SIZE(gc0313_fmts) - 1; |
| |
| return i; |
| } |
| |
| /* Find a frame size in an array */ |
| static int gc0313_find_framesize(u32 width, u32 height) |
| { |
| int i; |
| |
| for (i = 0; i < GC0313_SIZE_LAST; i++) { |
| if ((gc0313_frmsizes[i].width >= width) && |
| (gc0313_frmsizes[i].height >= height)) |
| break; |
| } |
| |
| /* If not found, select biggest */ |
| if (i >= GC0313_SIZE_LAST) |
| i = GC0313_SIZE_LAST - 1; |
| |
| return i; |
| } |
| |
| struct gc0313 { |
| struct v4l2_subdev subdev; |
| struct v4l2_subdev_sensor_interface_parms *plat_parms; |
| int i_size; |
| int i_fmt; |
| int brightness; |
| int contrast; |
| int colorlevel; |
| int sharpness; |
| int saturation; |
| int antibanding; |
| int whitebalance; |
| int framerate; |
| }; |
| |
| static struct gc0313 *to_gc0313(const struct i2c_client *client) |
| { |
| return container_of(i2c_get_clientdata(client), struct gc0313, subdev); |
| } |
| |
| |
| static const struct gc0313_timing_cfg timing_cfg_yuv[GC0313_SIZE_LAST] = { |
| [GC0313_SIZE_QVGA] = { |
| /* Timing control 2624 x 1952 --> 2560 x 1920 */ |
| .x_addr_start = 32, |
| .y_addr_start = 16, |
| .x_addr_end = 2591, |
| .y_addr_end = 1935, |
| /* Output image size */ |
| .h_output_size = 320, |
| .v_output_size = 240, |
| /* ISP Windowing size 2560 x 1920 --> 2560 x 1920 */ |
| .isp_h_offset = 0, |
| .isp_v_offset = 0, |
| /* Total size (+blanking) */ |
| .h_total_size = 2200, |
| .v_total_size = 1280, |
| /* Sensor Read Binning Enabled */ |
| .h_odd_ss_inc = 3, |
| .h_even_ss_inc = 1, |
| .v_odd_ss_inc = 3, |
| .v_even_ss_inc = 1, |
| .out_mode_sel = 0x07, |
| .sclk_dividers = 0x01, |
| .sys_mipi_clk = 0x11, |
| }, |
| [GC0313_SIZE_VGA] = { |
| /* Timing control 2624 x 1952 --> 2560 x 1920 */ |
| .x_addr_start = 32, |
| .y_addr_start = 16, |
| .x_addr_end = 2591, |
| .y_addr_end = 1935, |
| /* Output image size */ |
| .h_output_size = 640, |
| .v_output_size = 480, |
| /* ISP Windowing size 2560 x 1920 --> 2560 x 1920 */ |
| .isp_h_offset = 0, |
| .isp_v_offset = 0, |
| /* Total size (+blanking) */ |
| .h_total_size = 2200, |
| .v_total_size = 1280, |
| /* Sensor Read Binning Enabled */ |
| .h_odd_ss_inc = 3, |
| .h_even_ss_inc = 1, |
| .v_odd_ss_inc = 3, |
| .v_even_ss_inc = 1, |
| .out_mode_sel = 0x07, |
| .sclk_dividers = 0x01, |
| .sys_mipi_clk = 0x11, |
| }, |
| }; |
| |
| static const struct gc0313_timing_cfg timing_cfg_jpeg[GC0313_SIZE_LAST] = { |
| [GC0313_SIZE_QVGA] = { |
| /* Timing control 2624 x 1952 --> 2560 x 1920 */ |
| .x_addr_start = 32, |
| .y_addr_start = 16, |
| .x_addr_end = 2591, |
| .y_addr_end = 1935, |
| /* Output image size */ |
| .h_output_size = 320, |
| .v_output_size = 240, |
| /* ISP Windowing size 2560 x 1920 --> 2560 x 1920 */ |
| .isp_h_offset = 0, |
| .isp_v_offset = 0, |
| /* Total size (+blanking) */ |
| .h_total_size = 2844, |
| .v_total_size = 1968, |
| /* Sensor Read Binning Disabled */ |
| .h_odd_ss_inc = 1, |
| .h_even_ss_inc = 1, |
| .v_odd_ss_inc = 1, |
| .v_even_ss_inc = 1, |
| .out_mode_sel = 0x26, |
| .sclk_dividers = 0x01, |
| .sys_mipi_clk = 0x12, |
| }, |
| [GC0313_SIZE_VGA] = { |
| /* Timing control 2624 x 1952 --> 2560 x 1920 */ |
| .x_addr_start = 32, |
| .y_addr_start = 16, |
| .x_addr_end = 2591, |
| .y_addr_end = 1935, |
| /* Output image size */ |
| .h_output_size = 640, |
| .v_output_size = 480, |
| /* ISP Windowing size 2560 x 1920 --> 2560 x 1920 */ |
| .isp_h_offset = 0, |
| .isp_v_offset = 0, |
| /* Total size (+blanking) */ |
| .h_total_size = 2844, |
| .v_total_size = 1968, |
| /* Sensor Read Binning Disabled */ |
| .h_odd_ss_inc = 1, |
| .h_even_ss_inc = 1, |
| .v_odd_ss_inc = 1, |
| .v_even_ss_inc = 1, |
| .out_mode_sel = 0x26, |
| .sclk_dividers = 0x01, |
| .sys_mipi_clk = 0x12, |
| }, |
| }; |
| |
| #if 0 |
| static struct v4l2_subdev_sensor_serial_parms mipi_cfgs[GC0313_SIZE_LAST] = { |
| [GC0313_SIZE_QVGA] = { |
| .lanes = 1, |
| .channel = 0, |
| .phy_rate = (336 * 2 * 1000000), |
| .pix_clk = 21, /* Revisit */ |
| }, |
| [GC0313_SIZE_VGA] = { |
| .lanes = 1, |
| .channel = 0, |
| .phy_rate = (336 * 2 * 1000000), |
| .pix_clk = 21, /* Revisit */ |
| }, |
| }; |
| #endif |
| |
| /** |
| *gc0313_read_smbus - Read a value from a register in an gc0313 sensor device |
| *@client: i2c driver client structure |
| *@reg: register address / offset |
| *@val: stores the value that gets read |
| * |
| * Read a value from a register in an gc0313 sensor device. |
| * The value is returned in 'val'. |
| * Returns zero if successful, or non-zero otherwise. |
| */ |
| |
| /* |
| * Low-level register I/O. |
| * |
| * Note that there are two versions of these. On the XO 1, the |
| * i2c controller only does SMBUS, so that's what we use. The |
| * ov7670 is not really an SMBUS device, though, so the communication |
| * is not always entirely reliable. |
| */ |
| static int gc0313_read_smbus(struct i2c_client *client, unsigned char reg, |
| unsigned char *value) |
| { |
| int ret; |
| ret = i2c_smbus_read_byte_data(client, reg); |
| if (ret >= 0) { |
| *value = (unsigned char)ret; |
| ret = 0; |
| } |
| return ret; |
| } |
| |
| /** |
| * Write a value to a register in gc0313 sensor device. |
| *@client: i2c driver client structure. |
| *@reg: Address of the register to read value from. |
| *@val: Value to be written to a specific register. |
| * Returns zero if successful, or non-zero otherwise. |
| */ |
| static int gc0313_write_smbus(struct i2c_client *client, unsigned char reg, |
| unsigned char value) |
| { |
| int ret = i2c_smbus_write_byte_data(client, reg, value); |
| |
| if (0 != ret) |
| printk(KERN_INFO "Error write reg"); |
| |
| return ret; |
| } |
| |
| |
| |
| static const struct v4l2_queryctrl gc0313_controls[] = { |
| { |
| .id = V4L2_CID_CAMERA_BRIGHTNESS, |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .name = "Brightness", |
| .minimum = EV_MINUS_1, |
| .maximum = EV_PLUS_1, |
| .step = 1, |
| .default_value = EV_DEFAULT, |
| }, |
| { |
| .id = V4L2_CID_CAMERA_CONTRAST, |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .name = "Contrast", |
| .minimum = CONTRAST_MINUS_1, |
| .maximum = CONTRAST_PLUS_1, |
| .step = 1, |
| .default_value = CONTRAST_DEFAULT, |
| }, |
| { |
| .id = V4L2_CID_CAMERA_EFFECT, |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .name = "Color Effects", |
| .minimum = IMAGE_EFFECT_NONE, |
| .maximum = (1 << IMAGE_EFFECT_NONE | 1 << IMAGE_EFFECT_SEPIA | |
| 1 << IMAGE_EFFECT_BNW | 1 << IMAGE_EFFECT_NEGATIVE), |
| .step = 1, |
| .default_value = IMAGE_EFFECT_NONE, |
| }, |
| { |
| .id = V4L2_CID_CAMERA_ANTI_BANDING, |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .name = "Anti Banding", |
| .minimum = ANTI_BANDING_AUTO, |
| .maximum = ANTI_BANDING_60HZ, |
| .step = 1, |
| .default_value = ANTI_BANDING_AUTO, |
| }, |
| { |
| .id = V4L2_CID_CAMERA_WHITE_BALANCE, |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .name = "White Balance", |
| .minimum = WHITE_BALANCE_AUTO, |
| .maximum = WHITE_BALANCE_FLUORESCENT, |
| .step = 1, |
| .default_value = WHITE_BALANCE_AUTO, |
| }, |
| { |
| .id = V4L2_CID_CAMERA_FRAME_RATE, |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .name = "Framerate control", |
| .minimum = FRAME_RATE_AUTO, |
| .maximum = (1 << FRAME_RATE_AUTO | 1 << FRAME_RATE_5 | |
| 1 << FRAME_RATE_10 | 1 << FRAME_RATE_15 | |
| 1 << FRAME_RATE_25 | 1 << FRAME_RATE_30), |
| .step = 1, |
| .default_value = FRAME_RATE_AUTO, |
| }, |
| |
| }; |
| |
| /** |
| * Initialize a list of gc0313 registers. |
| * The list of registers is terminated by the pair of values |
| *@client: i2c driver client structure. |
| *@reglist[]: List of address of the registers to write data. |
| * Returns zero if successful, or non-zero otherwise. |
| */ |
| static int gc0313_write_smbuss(struct i2c_client *client, |
| const struct gc0313_reg reglist[]) |
| { |
| int err = 0, index; |
| |
| for (index = 0; (((reglist[index].reg != 0xff) |
| || (reglist[index].val != 0xff)) |
| && (err == 0)); index++) { |
| err |= gc0313_write_smbus(client, reglist[index].reg, |
| reglist[index].val); |
| } |
| |
| printk(KERN_INFO "%s: index=%d err=%d\n", __func__, index, err); |
| return err; |
| } |
| |
| |
| static int gc0313_config_timing(struct i2c_client *client) |
| { |
| struct gc0313 *gc0313 = to_gc0313(client); |
| int ret = 0, i = gc0313->i_size; |
| const struct gc0313_timing_cfg *timing_cfg; |
| |
| if (gc0313_fmts[gc0313->i_fmt].code == V4L2_MBUS_FMT_JPEG_1X8) |
| timing_cfg = &timing_cfg_jpeg[i]; |
| else |
| timing_cfg = &timing_cfg_yuv[i]; |
| |
| |
| /* TODO: Do the right thing here, and validate mipi timing params */ |
| |
| return ret; |
| } |
| |
| static int gc0313_s_stream(struct v4l2_subdev *sd, int enable) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret = 0; |
| |
| return ret; |
| } |
| |
| static int gc0313_set_bus_param(struct soc_camera_device *icd, |
| unsigned long flags) |
| { |
| /* TODO: Do the right thing here, and validate bus params */ |
| return 0; |
| } |
| |
| static unsigned long gc0313_query_bus_param(struct soc_camera_device *icd) |
| { |
| unsigned long flags = SOCAM_PCLK_SAMPLE_FALLING | |
| SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | |
| SOCAM_DATA_ACTIVE_HIGH | SOCAM_MASTER; |
| |
| /* TODO: Do the right thing here, and validate bus params */ |
| |
| flags |= SOCAM_DATAWIDTH_10; |
| |
| return flags; |
| } |
| |
| static int gc0313_enum_input(struct soc_camera_device *icd, |
| struct v4l2_input *inp) |
| { |
| struct soc_camera_link *icl = to_soc_camera_link(icd); |
| struct v4l2_subdev_sensor_interface_parms *plat_parms; |
| |
| inp->type = V4L2_INPUT_TYPE_CAMERA; |
| inp->std = V4L2_STD_UNKNOWN; |
| strcpy(inp->name, "gc0313"); |
| |
| if (icl && icl->priv) { |
| |
| plat_parms = icl->priv; |
| inp->status = 0; |
| |
| if (plat_parms->orientation == V4L2_SUBDEV_SENSOR_PORTRAIT) |
| inp->status |= V4L2_IN_ST_HFLIP; |
| |
| if (plat_parms->facing == V4L2_SUBDEV_SENSOR_BACK) |
| inp->status |= V4L2_IN_ST_BACK; |
| |
| } |
| return 0; |
| } |
| |
| static int gc0313_g_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct gc0313 *gc0313 = to_gc0313(client); |
| |
| dev_dbg(&client->dev, "gc0313_g_fmt\n"); |
| |
| mf->width = gc0313_frmsizes[gc0313->i_size].width; |
| mf->height = gc0313_frmsizes[gc0313->i_size].height; |
| mf->code = gc0313_fmts[gc0313->i_fmt].code; |
| mf->colorspace = gc0313_fmts[gc0313->i_fmt].colorspace; |
| mf->field = V4L2_FIELD_NONE; |
| |
| return 0; |
| } |
| |
| static int gc0313_try_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) |
| { |
| int i_fmt; |
| int i_size; |
| |
| i_fmt = gc0313_find_datafmt(mf->code); |
| |
| mf->code = gc0313_fmts[i_fmt].code; |
| mf->colorspace = gc0313_fmts[i_fmt].colorspace; |
| mf->field = V4L2_FIELD_NONE; |
| |
| i_size = gc0313_find_framesize(mf->width, mf->height); |
| |
| mf->width = gc0313_frmsizes[i_size].width; |
| mf->height = gc0313_frmsizes[i_size].height; |
| |
| return 0; |
| } |
| |
| static int gc0313_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct gc0313 *gc0313 = to_gc0313(client); |
| int ret = 0; |
| |
| dev_dbg(&client->dev, "gc0313_s_fmt\n"); |
| |
| |
| ret = gc0313_try_fmt(sd, mf); |
| if (ret < 0) |
| return ret; |
| |
| ret = gc0313_write_smbuss(client, configscript_common1); |
| |
| gc0313->i_size = gc0313_find_framesize(mf->width, mf->height); |
| gc0313->i_fmt = gc0313_find_datafmt(mf->code); |
| |
| switch ((u32) gc0313_fmts[gc0313->i_fmt].code) { |
| case V4L2_MBUS_FMT_UYVY8_2X8: |
| break; |
| case V4L2_MBUS_FMT_YUYV8_2X8: |
| break; |
| case V4L2_MBUS_FMT_JPEG_1X8: |
| break; |
| default: |
| /* This shouldn't happen */ |
| ret = -EINVAL; |
| return ret; |
| } |
| |
| ret = gc0313_config_timing(client); |
| if (ret) |
| return ret; |
| |
| return ret; |
| } |
| |
| static int gc0313_g_chip_ident(struct v4l2_subdev *sd, |
| struct v4l2_dbg_chip_ident *id) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR) |
| return -EINVAL; |
| |
| if (id->match.addr != client->addr) |
| return -ENODEV; |
| |
| id->ident = V4L2_IDENT_OV7692; |
| id->revision = 0; |
| |
| return 0; |
| } |
| |
| static int gc0313_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct gc0313 *gc0313 = to_gc0313(client); |
| |
| dev_dbg(&client->dev, "gc0313_g_ctrl\n"); |
| |
| switch (ctrl->id) { |
| case V4L2_CID_CAMERA_BRIGHTNESS: |
| ctrl->value = gc0313->brightness; |
| break; |
| case V4L2_CID_CAMERA_CONTRAST: |
| ctrl->value = gc0313->contrast; |
| break; |
| case V4L2_CID_CAMERA_EFFECT: |
| ctrl->value = gc0313->colorlevel; |
| break; |
| case V4L2_CID_SATURATION: |
| ctrl->value = gc0313->saturation; |
| break; |
| case V4L2_CID_SHARPNESS: |
| ctrl->value = gc0313->sharpness; |
| break; |
| case V4L2_CID_CAMERA_ANTI_BANDING: |
| ctrl->value = gc0313->antibanding; |
| break; |
| case V4L2_CID_CAMERA_WHITE_BALANCE: |
| ctrl->value = gc0313->whitebalance; |
| break; |
| case V4L2_CID_CAMERA_FRAME_RATE: |
| ctrl->value = gc0313->framerate; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int gc0313_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct gc0313 *gc0313 = to_gc0313(client); |
| u8 ov_reg; |
| int ret = 0; |
| |
| dev_dbg(&client->dev, "gc0313_s_ctrl\n"); |
| |
| switch (ctrl->id) { |
| case V4L2_CID_CAMERA_BRIGHTNESS: |
| |
| if (ctrl->value > EV_PLUS_1) |
| return -EINVAL; |
| |
| gc0313->brightness = ctrl->value; |
| switch (gc0313->brightness) { |
| case EV_MINUS_1: |
| ret = gc0313_write_smbuss(client, |
| gc0313_brightness_lv4_tbl); |
| break; |
| case EV_PLUS_1: |
| ret = gc0313_write_smbuss(client, |
| gc0313_brightness_lv0_tbl); |
| break; |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_brightness_lv2_default_tbl); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| case V4L2_CID_CAMERA_CONTRAST: |
| |
| if (ctrl->value > CONTRAST_PLUS_1) |
| return -EINVAL; |
| |
| gc0313->contrast = ctrl->value; |
| switch (gc0313->contrast) { |
| case CONTRAST_MINUS_1: |
| ret = gc0313_write_smbuss(client, |
| gc0313_contrast_lv5_tbl); |
| break; |
| case CONTRAST_PLUS_1: |
| ret = gc0313_write_smbuss(client, |
| gc0313_contrast_lv0_tbl); |
| break; |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_contrast_default_lv3_tbl); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| case V4L2_CID_CAMERA_EFFECT: |
| |
| if (ctrl->value > IMAGE_EFFECT_BNW) |
| return -EINVAL; |
| |
| gc0313->colorlevel = ctrl->value; |
| |
| switch (gc0313->colorlevel) { |
| case IMAGE_EFFECT_BNW: |
| ret = gc0313_write_smbuss(client, |
| gc0313_effect_bw_tbl); |
| break; |
| case IMAGE_EFFECT_SEPIA: |
| ret = gc0313_write_smbuss(client, |
| gc0313_effect_sepia_tbl); |
| break; |
| case IMAGE_EFFECT_NEGATIVE: |
| ret = gc0313_write_smbuss(client, |
| gc0313_effect_negative_tbl); |
| break; |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_effect_normal_tbl); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| case V4L2_CID_SATURATION: |
| |
| if (ctrl->value > GC0313_SATURATION_MAX) |
| return -EINVAL; |
| |
| gc0313->saturation = ctrl->value; |
| switch (gc0313->saturation) { |
| case GC0313_SATURATION_MIN: |
| ret = gc0313_write_smbuss(client, |
| gc0313_saturation_lv0_tbl); |
| break; |
| case GC0313_SATURATION_MAX: |
| ret = gc0313_write_smbuss(client, |
| gc0313_saturation_lv5_tbl); |
| break; |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_saturation_default_lv3_tbl); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| case V4L2_CID_SHARPNESS: |
| |
| if (ctrl->value > GC0313_SHARPNESS_MAX) |
| return -EINVAL; |
| |
| gc0313->sharpness = ctrl->value; |
| switch (gc0313->sharpness) { |
| case GC0313_SHARPNESS_MIN: |
| ret = gc0313_write_smbuss(client, |
| gc0313_sharpness_lv0_tbl); |
| break; |
| case GC0313_SHARPNESS_MAX: |
| ret = gc0313_write_smbuss(client, |
| gc0313_sharpness_lv3_tbl); |
| break; |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_sharpness_default_lv2_tbl); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| |
| case V4L2_CID_CAMERA_ANTI_BANDING: |
| |
| if (ctrl->value > ANTI_BANDING_60HZ) |
| return -EINVAL; |
| |
| gc0313->antibanding = ctrl->value; |
| |
| switch (gc0313->antibanding) { |
| case ANTI_BANDING_50HZ: |
| ret = gc0313_write_smbuss(client, |
| gc0313_antibanding_50z_tbl); |
| break; |
| case ANTI_BANDING_60HZ: |
| ret = gc0313_write_smbuss(client, |
| gc0313_antibanding_60z_tbl); |
| break; |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_antibanding_auto_tbl); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| |
| case V4L2_CID_CAMERA_WHITE_BALANCE: |
| |
| if (ctrl->value > WHITE_BALANCE_FLUORESCENT) |
| return -EINVAL; |
| |
| gc0313->whitebalance = ctrl->value; |
| |
| if (ret) |
| return ret; |
| |
| switch (gc0313->whitebalance) { |
| case WHITE_BALANCE_FLUORESCENT: |
| ov_reg |= 0x01; |
| ret = gc0313_write_smbuss(client, |
| gc0313_wb_fluorescent); |
| break; |
| case WHITE_BALANCE_SUNNY: |
| ov_reg |= 0x01; |
| ret = gc0313_write_smbuss(client, |
| gc0313_wb_daylight); |
| break; |
| case WHITE_BALANCE_CLOUDY: |
| ov_reg |= 0x01; |
| ret = gc0313_write_smbuss(client, |
| gc0313_wb_cloudy); |
| break; |
| case WHITE_BALANCE_TUNGSTEN: |
| ov_reg |= 0x01; |
| ret = gc0313_write_smbuss(client, |
| gc0313_wb_tungsten); |
| break; |
| default: |
| ov_reg &= ~(0x01); |
| ret = gc0313_write_smbuss(client, |
| gc0313_wb_def); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| |
| case V4L2_CID_CAMERA_FRAME_RATE: |
| |
| if (ctrl->value > FRAME_RATE_30) |
| return -EINVAL; |
| |
| if ((gc0313->i_size < GC0313_SIZE_QVGA) || |
| (gc0313->i_size > GC0313_SIZE_VGA)) { |
| if (ctrl->value == FRAME_RATE_30 || |
| ctrl->value == FRAME_RATE_AUTO) |
| return 0; |
| else |
| return -EINVAL; |
| } |
| |
| gc0313->framerate = ctrl->value; |
| |
| switch (gc0313->framerate) { |
| case FRAME_RATE_5: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_5); |
| break; |
| case FRAME_RATE_7: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_7); |
| break; |
| case FRAME_RATE_10: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_10); |
| break; |
| case FRAME_RATE_15: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_15); |
| break; |
| case FRAME_RATE_20: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_20); |
| break; |
| case FRAME_RATE_25: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_25); |
| break; |
| case FRAME_RATE_30: |
| case FRAME_RATE_AUTO: |
| default: |
| ret = gc0313_write_smbuss(client, |
| gc0313_fps_30); |
| break; |
| } |
| if (ret) |
| return ret; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static long gc0313_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) |
| { |
| int ret = 0; |
| |
| switch (cmd) { |
| case VIDIOC_THUMB_SUPPORTED: |
| { |
| int *p = arg; |
| *p = 0; /* no we don't support thumbnail */ |
| break; |
| } |
| case VIDIOC_JPEG_G_PACKET_INFO: |
| { |
| struct v4l2_jpeg_packet_info *p = |
| (struct v4l2_jpeg_packet_info *)arg; |
| p->padded = 0; |
| p->packet_size = 0x400; |
| break; |
| } |
| |
| case VIDIOC_SENSOR_G_OPTICAL_INFO: |
| { |
| struct v4l2_sensor_optical_info *p = |
| (struct v4l2_sensor_optical_info *)arg; |
| /* assuming 67.5 degree diagonal viewing angle */ |
| p->hor_angle.numerator = 5401; |
| p->hor_angle.denominator = 100; |
| p->ver_angle.numerator = 3608; |
| p->ver_angle.denominator = 100; |
| p->focus_distance[0] = 10; /* near focus in cm */ |
| p->focus_distance[1] = 100; /* optimal focus in cm */ |
| p->focus_distance[2] = -1; /* infinity */ |
| p->focal_length.numerator = 342; |
| p->focal_length.denominator = 100; |
| break; |
| } |
| default: |
| ret = -ENOIOCTLCMD; |
| break; |
| } |
| return ret; |
| } |
| |
| #ifdef CONFIG_VIDEO_ADV_DEBUG |
| static int gc0313_g_register(struct v4l2_subdev *sd, |
| struct v4l2_dbg_register *reg) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->size > 2) |
| return -EINVAL; |
| |
| if (reg->match.addr != client->addr) |
| return -ENODEV; |
| |
| reg->size = 2; |
| if (gc0313_read_smbus(client, reg->reg, ®->val)) |
| return -EIO return 0; |
| } |
| |
| static int gc0313_s_register(struct v4l2_subdev *sd, |
| struct v4l2_dbg_register *reg) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| |
| if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->size > 2) |
| return -EINVAL; |
| |
| if (reg->match.addr != client->addr) |
| return -ENODEV; |
| |
| if (gc0313_write_smbus(client, reg->reg, reg->val)) |
| return -EIO; |
| |
| return 0; |
| } |
| #endif |
| |
| static struct soc_camera_ops gc0313_ops = { |
| .set_bus_param = gc0313_set_bus_param, |
| .query_bus_param = gc0313_query_bus_param, |
| .enum_input = gc0313_enum_input, |
| .controls = gc0313_controls, |
| .num_controls = ARRAY_SIZE(gc0313_controls), |
| }; |
| |
| |
| static int gc0313_init(struct i2c_client *client) |
| { |
| struct gc0313 *gc0313 = to_gc0313(client); |
| int ret = 0; |
| |
| printk(KERN_INFO "%s: Sensor initialized!\n", __func__); |
| |
| ret = gc0313_write_smbuss(client, configscript_common1); |
| if (ret) { |
| printk(KERN_INFO "%s: Sensor initialized failed!\n", __func__); |
| goto out; |
| } |
| |
| /* default brightness and contrast */ |
| gc0313->brightness = EV_DEFAULT; |
| gc0313->contrast = CONTRAST_DEFAULT; |
| gc0313->colorlevel = IMAGE_EFFECT_NONE; |
| gc0313->antibanding = ANTI_BANDING_AUTO; |
| gc0313->whitebalance = WHITE_BALANCE_AUTO; |
| gc0313->framerate = FRAME_RATE_AUTO; |
| |
| dev_dbg(&client->dev, "Sensor initialized\n"); |
| |
| out: |
| return ret; |
| } |
| |
| /* |
| * Interface active, can use i2c. If it fails, it can indeed mean, that |
| *this wasn't our capture interface, so, we wait for the right one |
| */ |
| static int gc0313_video_probe(struct soc_camera_device *icd, |
| struct i2c_client *client) |
| { |
| unsigned long flags; |
| int ret = 0; |
| u8 revision = 0; |
| u16 sensor_id = 0; |
| u8 value = 0; |
| |
| /* |
| * We must have a parent by now. And it cannot be a wrong one. |
| * So this entire test is completely redundant. |
| */ |
| if (!icd->dev.parent || |
| to_soc_camera_host(icd->dev.parent)->nr != icd->iface) |
| return -ENODEV; |
| |
| ret = gc0313_read_smbus(client, 0xf0, &sensor_id); |
| ret = gc0313_read_smbus(client, 0xf0, &value); |
| sensor_id = ((sensor_id&0xff) << 8) | (value&0xff); |
| |
| printk(KERN_INFO "%s: REG_MIDH = 0x%x\n", __func__, sensor_id); |
| |
| if (ret < 0) |
| return ret; |
| if (GC0313_ID != sensor_id) |
| return -ENODEV; |
| |
| return ret; |
| } |
| |
| static void gc0313_video_remove(struct soc_camera_device *icd) |
| { |
| dev_dbg(&icd->dev, "Video removed: %p, %p\n", |
| icd->dev.parent, icd->vdev); |
| } |
| |
| static struct v4l2_subdev_core_ops gc0313_subdev_core_ops = { |
| .g_chip_ident = gc0313_g_chip_ident, |
| .g_ctrl = gc0313_g_ctrl, |
| .s_ctrl = gc0313_s_ctrl, |
| .ioctl = gc0313_ioctl, |
| #ifdef CONFIG_VIDEO_ADV_DEBUG |
| .g_register = gc0313_g_register, |
| .s_register = gc0313_s_register, |
| #endif |
| }; |
| |
| static int gc0313_enum_fmt(struct v4l2_subdev *sd, unsigned int index, |
| enum v4l2_mbus_pixelcode *code) |
| { |
| if (index >= ARRAY_SIZE(gc0313_fmts)) |
| return -EINVAL; |
| |
| *code = gc0313_fmts[index].code; |
| return 0; |
| } |
| |
| static int gc0313_enum_framesizes(struct v4l2_subdev *sd, |
| struct v4l2_frmsizeenum *fsize) |
| { |
| if (fsize->index >= GC0313_SIZE_LAST) |
| return -EINVAL; |
| |
| fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; |
| fsize->pixel_format = V4L2_PIX_FMT_UYVY; |
| |
| fsize->discrete = gc0313_frmsizes[fsize->index]; |
| |
| return 0; |
| } |
| |
| /* we only support fixed frame rate */ |
| static int gc0313_enum_frameintervals(struct v4l2_subdev *sd, |
| struct v4l2_frmivalenum *interval) |
| { |
| int size; |
| |
| if (interval->index >= 1) |
| return -EINVAL; |
| |
| interval->type = V4L2_FRMIVAL_TYPE_DISCRETE; |
| |
| size = gc0313_find_framesize(interval->width, interval->height); |
| |
| switch (size) { |
| case GC0313_SIZE_VGA: |
| case GC0313_SIZE_QVGA: |
| default: |
| interval->discrete.numerator = 1; |
| interval->discrete.denominator = 24; |
| break; |
| } |
| /* printk(KERN_ERR"%s: width=%d height=%d fi=%d/%d\n", __func__, |
| interval->width, |
| interval->height, interval->discrete.numerator, |
| interval->discrete.denominator); |
| */ |
| return 0; |
| } |
| |
| static int gc0313_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *param) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct gc0313 *gc0313 = to_gc0313(client); |
| struct v4l2_captureparm *cparm; |
| |
| if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| |
| cparm = ¶m->parm.capture; |
| |
| memset(param, 0, sizeof(*param)); |
| param->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| |
| cparm->capability = V4L2_CAP_TIMEPERFRAME; |
| |
| switch (gc0313->i_size) { |
| case GC0313_SIZE_VGA: |
| case GC0313_SIZE_QVGA: |
| default: |
| cparm->timeperframe.numerator = 1; |
| cparm->timeperframe.denominator = 24; |
| break; |
| } |
| |
| return 0; |
| } |
| static int gc0313_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *param) |
| { |
| /* |
| * FIXME: This just enforces the hardcoded framerates until this is |
| *flexible enough. |
| */ |
| return gc0313_g_parm(sd, param); |
| } |
| |
| static struct v4l2_subdev_video_ops gc0313_subdev_video_ops = { |
| .s_stream = gc0313_s_stream, |
| .s_mbus_fmt = gc0313_s_fmt, |
| .g_mbus_fmt = gc0313_g_fmt, |
| .try_mbus_fmt = gc0313_try_fmt, |
| .enum_mbus_fmt = gc0313_enum_fmt, |
| .enum_mbus_fsizes = gc0313_enum_framesizes, |
| .enum_framesizes = gc0313_enum_framesizes, |
| .enum_frameintervals = gc0313_enum_frameintervals, |
| .g_parm = gc0313_g_parm, |
| .s_parm = gc0313_s_parm, |
| }; |
| static int gc0313_g_skip_frames(struct v4l2_subdev *sd, u32 *frames) |
| { |
| /* Quantity of initial bad frames to skip. Revisit. */ |
| *frames = 5; |
| |
| return 0; |
| } |
| |
| static int gc0313_g_interface_parms(struct v4l2_subdev *sd, |
| struct v4l2_subdev_sensor_interface_parms |
| *parms) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct gc0313 *gc0313 = to_gc0313(client); |
| u8 sclk_dividers; |
| |
| if (!parms) |
| return -EINVAL; |
| |
| parms->if_type = gc0313->plat_parms->if_type; |
| parms->if_mode = gc0313->plat_parms->if_mode; |
| parms->parms = gc0313->plat_parms->parms; |
| |
| /* set the hs term time */ |
| if (gc0313_fmts[gc0313->i_fmt].code == V4L2_MBUS_FMT_JPEG_1X8) |
| sclk_dividers = timing_cfg_jpeg[gc0313->i_size].sclk_dividers; |
| else |
| sclk_dividers = timing_cfg_yuv[gc0313->i_size].sclk_dividers; |
| |
| if (sclk_dividers == 0x01) |
| parms->parms.serial.hs_term_time = 0x01; |
| else |
| parms->parms.serial.hs_term_time = 0x08; |
| |
| return 0; |
| } |
| |
| |
| |
| static struct v4l2_subdev_sensor_ops gc0313_subdev_sensor_ops = { |
| .g_skip_frames = gc0313_g_skip_frames, |
| .g_interface_parms = gc0313_g_interface_parms, |
| }; |
| |
| static struct v4l2_subdev_ops gc0313_subdev_ops = { |
| .core = &gc0313_subdev_core_ops, |
| .video = &gc0313_subdev_video_ops, |
| .sensor = &gc0313_subdev_sensor_ops, |
| }; |
| |
| static int gc0313_probe(struct i2c_client *client, |
| const struct i2c_device_id *did) |
| { |
| struct gc0313 *gc0313; |
| struct soc_camera_device *icd = client->dev.platform_data; |
| struct soc_camera_link *icl; |
| int ret; |
| |
| client->addr = (0x42>>1); |
| |
| if (!icd) { |
| dev_err(&client->dev, "GC0313: missing soc-camera data!\n"); |
| return -EINVAL; |
| } |
| |
| icl = to_soc_camera_link(icd); |
| if (!icl) { |
| dev_err(&client->dev, "GC0313 driver needs platform data\n"); |
| return -EINVAL; |
| } |
| |
| if (!icl->priv) { |
| dev_err(&client->dev, |
| "GC0313 driver needs i/f platform data\n"); |
| return -EINVAL; |
| } |
| |
| gc0313 = kzalloc(sizeof(struct gc0313), GFP_KERNEL); |
| if (!gc0313) |
| return -ENOMEM; |
| |
| v4l2_i2c_subdev_init(&gc0313->subdev, client, &gc0313_subdev_ops); |
| |
| /* Second stage probe - when a capture adapter is there */ |
| icd->ops = &gc0313_ops; |
| |
| gc0313->i_size = GC0313_SIZE_VGA; |
| gc0313->i_fmt = 0; /* First format in the list */ |
| gc0313->plat_parms = icl->priv; |
| |
| |
| ret = gc0313_video_probe(icd, client); |
| if (ret) { |
| icd->ops = NULL; |
| kfree(gc0313); |
| return ret; |
| } |
| |
| msleep(20); |
| |
| /* init the sensor here */ |
| ret = gc0313_init(client); |
| if (ret) { |
| ret = -EINVAL; |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int gc0313_remove(struct i2c_client *client) |
| { |
| struct gc0313 *gc0313 = to_gc0313(client); |
| struct soc_camera_device *icd = client->dev.platform_data; |
| |
| icd->ops = NULL; |
| gc0313_video_remove(icd); |
| client->driver = NULL; |
| kfree(gc0313); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id gc0313_id[] = { |
| {"gc0313", 0}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, gc0313_id); |
| |
| static struct i2c_driver gc0313_i2c_driver = { |
| .driver = { |
| .name = "gc0313", |
| }, |
| .probe = gc0313_probe, |
| .remove = gc0313_remove, |
| .id_table = gc0313_id, |
| }; |
| |
| static int __init gc0313_mod_init(void) |
| { |
| return i2c_add_driver(&gc0313_i2c_driver); |
| } |
| |
| static void __exit gc0313_mod_exit(void) |
| { |
| i2c_del_driver(&gc0313_i2c_driver); |
| } |
| |
| module_init(gc0313_mod_init); |
| module_exit(gc0313_mod_exit); |
| |
| MODULE_DESCRIPTION("GC0313 Camera driver"); |
| MODULE_AUTHOR("BroadcomTK"); |
| MODULE_LICENSE("GPL v2"); |