/* * * Copyright (c) 2013 Intel Corporation. All Rights Reserved. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include "ap1302.h" #define to_ap1302_device(sub_dev) container_of(sub_dev, struct ap1302_device, sd) /* Static definitions */ static struct regmap_config ap1302_reg16_config = { .reg_bits = 16, .val_bits = 16, .reg_format_endian = REGMAP_ENDIAN_BIG, .val_format_endian = REGMAP_ENDIAN_BIG, }; static struct regmap_config ap1302_reg32_config = { .reg_bits = 16, .val_bits = 32, .reg_format_endian = REGMAP_ENDIAN_BIG, .val_format_endian = REGMAP_ENDIAN_BIG, }; static enum ap1302_contexts ap1302_cntx_mapping[] = { CONTEXT_PREVIEW, /* Invalid atomisp run mode */ CONTEXT_VIDEO, /* ATOMISP_RUN_MODE_VIDEO */ CONTEXT_SNAPSHOT, /* ATOMISP_RUN_MODE_STILL_CAPTURE */ CONTEXT_SNAPSHOT, /* ATOMISP_RUN_MODE_CONTINUOUS_CAPTURE */ CONTEXT_PREVIEW, /* ATOMISP_RUN_MODE_PREVIEW */ }; static struct ap1302_res_struct ap1302_preview_res[] = { { .width = 640, .height = 480, .fps = 30, }, { .width = 720, .height = 480, .fps = 30, }, { .width = 1280, .height = 720, .fps = 30, }, { .width = 1920, .height = 1080, .fps = 30, } }; static struct ap1302_res_struct ap1302_snapshot_res[] = { { .width = 640, .height = 480, .fps = 30, }, { .width = 720, .height = 480, .fps = 30, }, { .width = 1280, .height = 720, .fps = 30, }, { .width = 1920, .height = 1080, .fps = 30, } }; static struct ap1302_res_struct ap1302_video_res[] = { { .width = 640, .height = 480, .fps = 30, }, { .width = 720, .height = 480, .fps = 30, }, { .width = 1280, .height = 720, .fps = 30, }, { .width = 1920, .height = 1080, .fps = 30, } }; static enum ap1302_contexts stream_to_context[] = { CONTEXT_SNAPSHOT, CONTEXT_PREVIEW, CONTEXT_PREVIEW, CONTEXT_VIDEO }; static u16 aux_stream_config[CONTEXT_NUM][CONTEXT_NUM] = { {0, 0, 0}, /* Preview: No aux streams. */ {1, 0, 2}, /* Snapshot: 1 for postview. 2 for video */ {1, 0, 0}, /* Video: 1 for preview. */ }; static struct ap1302_context_info context_info[] = { {CNTX_WIDTH, AP1302_REG16, "width"}, {CNTX_HEIGHT, AP1302_REG16, "height"}, {CNTX_ROI_X0, AP1302_REG16, "roi_x0"}, {CNTX_ROI_X1, AP1302_REG16, "roi_x1"}, {CNTX_ROI_Y0, AP1302_REG16, "roi_y0"}, {CNTX_ROI_Y1, AP1302_REG16, "roi_y1"}, {CNTX_ASPECT, AP1302_REG16, "aspect"}, {CNTX_LOCK, AP1302_REG16, "lock"}, {CNTX_ENABLE, AP1302_REG16, "enable"}, {CNTX_OUT_FMT, AP1302_REG16, "out_fmt"}, {CNTX_SENSOR_MODE, AP1302_REG16, "sensor_mode"}, {CNTX_MIPI_CTRL, AP1302_REG16, "mipi_ctrl"}, {CNTX_MIPI_II_CTRL, AP1302_REG16, "mipi_ii_ctrl"}, {CNTX_LINE_TIME, AP1302_REG32, "line_time"}, {CNTX_MAX_FPS, AP1302_REG16, "max_fps"}, {CNTX_AE_USG, AP1302_REG16, "ae_usg"}, {CNTX_AE_UPPER_ET, AP1302_REG32, "ae_upper_et"}, {CNTX_AE_MAX_ET, AP1302_REG32, "ae_max_et"}, {CNTX_SS, AP1302_REG16, "ss"}, {CNTX_S1_SENSOR_MODE, AP1302_REG16, "s1_sensor_mode"}, {CNTX_HINF_CTRL, AP1302_REG16, "hinf_ctrl"}, }; /* This array stores the description list for metadata. The metadata contains exposure settings and face detection results. */ static u16 ap1302_ss_list[] = { 0xb01c, /* From 0x0186 with size 0x1C are exposure settings. */ 0x0186, 0xb002, /* 0x71c0 is for F-number */ 0x71c0, 0xb010, /* From 0x03dc with size 0x10 are face general infos. */ 0x03dc, 0xb0a0, /* From 0x03e4 with size 0xa0 are face detail infos. */ 0x03e4, 0xb020, /* From 0x0604 with size 0x20 are smile rate infos. */ 0x0604, 0x0000 }; /* End of static definitions */ static int ap1302_i2c_read_reg(struct v4l2_subdev *sd, u16 reg, u16 len, void *val) { struct ap1302_device *dev = to_ap1302_device(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); int ret; if (len == AP1302_REG16) ret = regmap_read(dev->regmap16, reg, val); else if (len == AP1302_REG32) ret = regmap_read(dev->regmap32, reg, val); else ret = -EINVAL; if (ret) { dev_dbg(&client->dev, "Read reg failed. reg=0x%04X\n", reg); return ret; } if (len == AP1302_REG16) dev_dbg(&client->dev, "read_reg[0x%04X] = 0x%04X\n", reg, *(u16*)val); else dev_dbg(&client->dev, "read_reg[0x%04X] = 0x%08X\n", reg, *(u32*)val); return ret; } static int ap1302_i2c_write_reg(struct v4l2_subdev *sd, u16 reg, u16 len, u32 val) { struct ap1302_device *dev = to_ap1302_device(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); int ret; if (len == AP1302_REG16) ret = regmap_write(dev->regmap16, reg, val); else if (len == AP1302_REG32) ret = regmap_write(dev->regmap32, reg, val); else ret = -EINVAL; if (ret) { dev_dbg(&client->dev, "Write reg failed. reg=0x%04X\n", reg); return ret; } if (len == AP1302_REG16) dev_dbg(&client->dev, "write_reg[0x%04X] = 0x%04X\n", reg, (u16)val); else dev_dbg(&client->dev, "write_reg[0x%04X] = 0x%08X\n", reg, (u32)val); return ret; } static u16 ap1302_calculate_context_reg_addr(enum ap1302_contexts context, u16 offset) { u16 reg_addr; /* The register offset is defined according to preview/video registers. Preview and video context have the same register definition. But snapshot context does not have register S1_SENSOR_MODE. When setting snapshot registers, if the offset exceeds S1_SENSOR_MODE, the actual offset needs to minus 2. */ if (context == CONTEXT_SNAPSHOT) { if (offset == CNTX_S1_SENSOR_MODE) return 0; if (offset > CNTX_S1_SENSOR_MODE) offset -= 2; } if (context == CONTEXT_PREVIEW) reg_addr = REG_PREVIEW_BASE + offset; else if (context == CONTEXT_VIDEO) reg_addr = REG_VIDEO_BASE + offset; else reg_addr = REG_SNAPSHOT_BASE + offset; return reg_addr; } static int ap1302_read_context_reg(struct v4l2_subdev *sd, enum ap1302_contexts context, u16 offset, u16 len) { struct ap1302_device *dev = to_ap1302_device(sd); u16 reg_addr = ap1302_calculate_context_reg_addr(context, offset); if (reg_addr == 0) return -EINVAL; return ap1302_i2c_read_reg(sd, reg_addr, len, ((u8*)&dev->cntx_config[context]) + offset); } static int ap1302_write_context_reg(struct v4l2_subdev *sd, enum ap1302_contexts context, u16 offset, u16 len) { struct ap1302_device *dev = to_ap1302_device(sd); u16 reg_addr = ap1302_calculate_context_reg_addr(context, offset); if (reg_addr == 0) return -EINVAL; return ap1302_i2c_write_reg(sd, reg_addr, len, *(u32*)(((u8*)&dev->cntx_config[context]) + offset)); } static int ap1302_dump_context_reg(struct v4l2_subdev *sd, enum ap1302_contexts context) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct ap1302_device *dev = to_ap1302_device(sd); int i; dev_dbg(&client->dev, "Dump registers for context[%d]:\n", context); for (i = 0; i < ARRAY_SIZE(context_info); i++) { struct ap1302_context_info *info = &context_info[i]; u8 *var = (u8*)&dev->cntx_config[context] + info->offset; /* Snapshot context does not have s1_sensor_mode register. */ if (context == CONTEXT_SNAPSHOT && info->offset == CNTX_S1_SENSOR_MODE) continue; ap1302_read_context_reg(sd, context, info->offset, info->len); if (info->len == AP1302_REG16) dev_dbg(&client->dev, "context.%s = 0x%04X (%d)\n", info->name, *(u16*)var, *(u16*)var); else dev_dbg(&client->dev, "context.%s = 0x%08X (%d)\n", info->name, *(u32*)var, *(u32*)var); } return 0; } static int ap1302_request_firmware(struct v4l2_subdev *sd) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct ap1302_device *dev = to_ap1302_device(sd); int ret; ret = request_firmware(&dev->fw, "ap1302_fw.bin", &client->dev); if (ret) dev_err(&client->dev, "ap1302_request_firmware failed. ret=%d\n", ret); return ret; } /* When loading firmware, host writes firmware data from address 0x8000. When the address reaches 0x9FFF, the next address should return to 0x8000. This function handles this address window and load firmware data to AP1302. win_pos indicates the offset within this window. Firmware loading procedure may call this function several times. win_pos records the current position that has been written to.*/ static int ap1302_write_fw_window(struct v4l2_subdev *sd, u16 *win_pos, const u8 *buf, u32 len) { struct ap1302_device *dev = to_ap1302_device(sd); int ret; u32 pos; u32 sub_len; for (pos = 0; pos < len; pos += sub_len) { if (len - pos < AP1302_FW_WINDOW_SIZE - *win_pos) sub_len = len - pos; else sub_len = AP1302_FW_WINDOW_SIZE - *win_pos; ret = regmap_raw_write(dev->regmap16, *win_pos + AP1302_FW_WINDOW_OFFSET, buf + pos, sub_len); if (ret) return ret; *win_pos += sub_len; if (*win_pos >= AP1302_FW_WINDOW_SIZE) *win_pos = 0; } return 0; } static int ap1302_load_firmware(struct v4l2_subdev *sd) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct ap1302_device *dev = to_ap1302_device(sd); const struct ap1302_firmware *fw; const u8* fw_data; u16 reg_val = 0; u16 win_pos = 0; int ret; dev_info(&client->dev, "Start to load firmware.\n"); if (!dev->fw) { dev_err(&client->dev, "firmware not requested.\n"); return -EINVAL; } fw = (const struct ap1302_firmware*) dev->fw->data; if (dev->fw->size != (sizeof(*fw) + fw->total_size)) { dev_err(&client->dev, "firmware size does not match.\n"); return -EINVAL; } /* The fw binary contains a header of struct ap1302_firmware. Following the header is the bootdata of AP1302. The bootdata pointer can be referenced as &fw[1]. */ fw_data = (u8*)&fw[1]; /* Clear crc register. */ ret = ap1302_i2c_write_reg(sd, REG_SIP_CRC, AP1302_REG16, 0xFFFF); if (ret) return ret; /* Load FW data for PLL init stage. */ ret = ap1302_write_fw_window(sd, &win_pos, fw_data, fw->pll_init_size); if (ret) return ret; /* Write 2 to bootdata_stage register to apply basic_init_hp settings and enable PLL. */ ret = ap1302_i2c_write_reg(sd, REG_BOOTDATA_STAGE, AP1302_REG16, 0x0002); if (ret) return ret; /* Wait 1ms for PLL to lock. */ msleep(1); /* Load the rest of bootdata content. */ ret = ap1302_write_fw_window(sd, &win_pos, fw_data + fw->pll_init_size, fw->total_size - fw->pll_init_size); if (ret) return ret; /* Check crc. */ ret = ap1302_i2c_read_reg(sd, REG_SIP_CRC, AP1302_REG16, ®_val); if (ret) return ret; if (reg_val != fw->crc) { dev_err(&client->dev, "crc does not match. T:0x%04X F:0x%04X\n", fw->crc, reg_val); return -EAGAIN; } /* Write 0xFFFF to bootdata_stage register to indicate AP1302 that the whole bootdata content has been loaded. */ ret = ap1302_i2c_write_reg(sd, REG_BOOTDATA_STAGE, AP1302_REG16, 0xFFFF); if (ret) return ret; dev_info(&client->dev, "Load firmware successfully.\n"); return 0; } static int __ap1302_s_power(struct v4l2_subdev *sd, int on, int load_fw) { struct ap1302_device *dev = to_ap1302_device(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); int ret, i; u16 ss_ptr; dev_info(&client->dev, "ap1302_s_power is called.\n"); ret = dev->platform_data->power_ctrl(sd, on); if (ret) { dev_err(&client->dev, "ap1302_s_power error. on=%d ret=%d\n", on, ret); return ret; } dev->power_on = on; if (!on || !load_fw) return 0; /* Load firmware after power on. */ ret = ap1302_load_firmware(sd); if (ret) { dev_err(&client->dev, "ap1302_load_firmware failed. ret=%d\n", ret); return ret; } ret = ap1302_i2c_read_reg(sd, REG_SS_HEAD_PT0, AP1302_REG16, &ss_ptr); if (ret) return ret; for (i = 0; i < ARRAY_SIZE(ap1302_ss_list); i++) { ret = ap1302_i2c_write_reg(sd, ss_ptr + i * 2, AP1302_REG16, ap1302_ss_list[i]); if (ret) return ret; } return ret; } static int ap1302_s_power(struct v4l2_subdev *sd, int on) { struct ap1302_device *dev = to_ap1302_device(sd); int ret; mutex_lock(&dev->input_lock); ret = __ap1302_s_power(sd, on, 1); dev->sys_activated = 0; mutex_unlock(&dev->input_lock); return ret; } static int ap1302_s_config(struct v4l2_subdev *sd, void *pdata) { struct ap1302_device *dev = to_ap1302_device(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); struct camera_mipi_info *mipi_info; u16 reg_val = 0; int ret; dev_info(&client->dev, "ap1302_s_config is called.\n"); if (pdata == NULL) return -ENODEV; dev->platform_data = pdata; mutex_lock(&dev->input_lock); if (dev->platform_data->platform_init) { ret = dev->platform_data->platform_init(client); if (ret) goto fail_power; } ret = __ap1302_s_power(sd, 1, 0); if (ret) goto fail_power; /* Detect for AP1302 */ ret = ap1302_i2c_read_reg(sd, REG_CHIP_VERSION, AP1302_REG16, ®_val); if (ret || (reg_val != AP1302_CHIP_ID)) { dev_err(&client->dev, "Chip version does no match. ret=%d ver=0x%04x\n", ret, reg_val); goto fail_config; } dev_info(&client->dev, "AP1302 Chip ID is 0x%X\n", reg_val); /* Detect revision for AP1302 */ ret = ap1302_i2c_read_reg(sd, REG_CHIP_REV, AP1302_REG16, ®_val); if (ret) goto fail_config; dev_info(&client->dev, "AP1302 Chip Rev is 0x%X\n", reg_val); ret = dev->platform_data->csi_cfg(sd, 1); if (ret) goto fail_config; mipi_info = v4l2_get_subdev_hostdata(sd); if (!mipi_info) goto fail_config; dev->num_lanes = mipi_info->num_lanes; ret = __ap1302_s_power(sd, 0, 0); if (ret) goto fail_power; mutex_unlock(&dev->input_lock); return ret; fail_config: __ap1302_s_power(sd, 0, 0); fail_power: mutex_unlock(&dev->input_lock); dev_err(&client->dev, "ap1302_s_config failed\n"); return ret; } static enum ap1302_contexts ap1302_get_context(struct v4l2_subdev *sd) { struct ap1302_device *dev = to_ap1302_device(sd); return dev->cur_context; } static int ap1302_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_mbus_code_enum *code) { if (code->index) return -EINVAL; code->code = V4L2_MBUS_FMT_UYVY8_1X16; return 0; } static int ap1302_match_resolution(struct ap1302_context_res *res, struct v4l2_mbus_framefmt *fmt) { s32 w0, h0, mismatch, distance; s32 w1 = fmt->width; s32 h1 = fmt->height; s32 min_distance = INT_MAX; s32 i, idx = -1; if (w1 == 0 || h1 == 0) return -1; for (i = 0; i < res->res_num; i++) { w0 = res->res_table[i].width; h0 = res->res_table[i].height; if (w0 < w1 || h0 < h1) continue; mismatch = abs(w0 * h1 - w1 * h0) * 8192 / w1 / h0; if (mismatch > 8192 * AP1302_MAX_RATIO_MISMATCH / 100) continue; distance = (w0 *h1 + w1 * h0) * 8192 / w1 / h1; if (distance < min_distance) { min_distance = distance; idx = i; } } return idx; } static s32 ap1302_try_mbus_fmt_locked(struct v4l2_subdev *sd, enum ap1302_contexts context, struct v4l2_mbus_framefmt *fmt) { struct ap1302_device *dev = to_ap1302_device(sd); struct ap1302_res_struct *res_table; s32 res_num, idx = -1; res_table = dev->cntx_res[context].res_table; res_num = dev->cntx_res[context].res_num; if ((fmt->width <= res_table[res_num - 1].width) && (fmt->height <= res_table[res_num - 1].height)) idx = ap1302_match_resolution(&dev->cntx_res[context], fmt); if (idx == -1) idx = res_num - 1; fmt->width = res_table[idx].width; fmt->height = res_table[idx].height; fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; return idx; } static int ap1302_try_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) { struct ap1302_device *dev = to_ap1302_device(sd); enum ap1302_contexts context; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); ap1302_try_mbus_fmt_locked(sd, context, fmt); mutex_unlock(&dev->input_lock); return 0; } static int ap1302_get_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) { struct ap1302_device *dev = to_ap1302_device(sd); enum ap1302_contexts context; struct ap1302_res_struct *res_table; s32 cur_res; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); res_table = dev->cntx_res[context].res_table; cur_res = dev->cntx_res[context].cur_res; fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; fmt->width = res_table[cur_res].width; fmt->height = res_table[cur_res].height; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_set_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) { struct ap1302_device *dev = to_ap1302_device(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); struct atomisp_input_stream_info *stream_info = (struct atomisp_input_stream_info*)fmt->reserved; enum ap1302_contexts context, main_context; mutex_lock(&dev->input_lock); context = stream_to_context[stream_info->stream]; dev_dbg(&client->dev, "ap1302_set_mbus_fmt. stream=%d context=%d\n", stream_info->stream, context); dev->cntx_res[context].cur_res = ap1302_try_mbus_fmt_locked(sd, context, fmt); dev->cntx_config[context].width = fmt->width; dev->cntx_config[context].height = fmt->height; ap1302_write_context_reg(sd, context, CNTX_WIDTH, AP1302_REG16); ap1302_write_context_reg(sd, context, CNTX_HEIGHT, AP1302_REG16); ap1302_read_context_reg(sd, context, CNTX_OUT_FMT, AP1302_REG16); dev->cntx_config[context].out_fmt &= ~OUT_FMT_TYPE_MASK; dev->cntx_config[context].out_fmt |= AP1302_FMT_UYVY422; ap1302_write_context_reg(sd, context, CNTX_OUT_FMT, AP1302_REG16); main_context = ap1302_get_context(sd); if (context == main_context) { ap1302_read_context_reg(sd, context, CNTX_MIPI_CTRL, AP1302_REG16); dev->cntx_config[context].mipi_ctrl &= ~MIPI_CTRL_IMGVC_MASK; dev->cntx_config[context].mipi_ctrl |= (context << MIPI_CTRL_IMGVC_OFFSET); dev->cntx_config[context].mipi_ctrl &= ~MIPI_CTRL_SSVC_MASK; dev->cntx_config[context].mipi_ctrl |= (context << MIPI_CTRL_SSVC_OFFSET); dev->cntx_config[context].mipi_ctrl &= ~MIPI_CTRL_SSTYPE_MASK; dev->cntx_config[context].mipi_ctrl |= (0x12 << MIPI_CTRL_SSTYPE_OFFSET); ap1302_write_context_reg(sd, context, CNTX_MIPI_CTRL, AP1302_REG16); ap1302_read_context_reg(sd, context, CNTX_SS, AP1302_REG16); dev->cntx_config[context].ss = AP1302_SS_CTRL; ap1302_write_context_reg(sd, context, CNTX_SS, AP1302_REG16); } else { /* Configure aux stream */ ap1302_read_context_reg(sd, context, CNTX_MIPI_II_CTRL, AP1302_REG16); dev->cntx_config[context].mipi_ii_ctrl &= ~MIPI_CTRL_IMGVC_MASK; dev->cntx_config[context].mipi_ii_ctrl |= (context << MIPI_CTRL_IMGVC_OFFSET); ap1302_write_context_reg(sd, context, CNTX_MIPI_II_CTRL, AP1302_REG16); if (stream_info->enable) { ap1302_read_context_reg(sd, main_context, CNTX_OUT_FMT, AP1302_REG16); dev->cntx_config[context].out_fmt |= (aux_stream_config[main_context][context] << OUT_FMT_IIS_OFFSET); ap1302_write_context_reg(sd, main_context, CNTX_OUT_FMT, AP1302_REG16); } } stream_info->ch_id = context; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_enum_framesizes(struct v4l2_subdev *sd, struct v4l2_frmsizeenum *fsize) { struct ap1302_device *dev = to_ap1302_device(sd); s32 index = fsize->index; enum ap1302_contexts context; struct ap1302_res_struct *res_table; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); if (index >= dev->cntx_res[context].res_num) { mutex_unlock(&dev->input_lock); return -EINVAL; } res_table = dev->cntx_res[context].res_table; fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; fsize->discrete.width = res_table[index].width; fsize->discrete.height = res_table[index].height; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_enum_frameintervals(struct v4l2_subdev *sd, struct v4l2_frmivalenum *fival) { struct ap1302_device *dev = to_ap1302_device(sd); enum ap1302_contexts context; struct ap1302_res_struct *res_table; unsigned int index = fival->index; s32 res_num, i; if (index > 0) return -EINVAL; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); res_table = dev->cntx_res[context].res_table; res_num = dev->cntx_res[context].res_num; /* find out the first equal or bigger size */ for (i = 0; i < res_num; i++) { if ((res_table[i].width >= fival->width) && (res_table[i].height >= fival->height)) break; } if (i == res_num) i--; fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; fival->width = res_table[i].width; fival->height = res_table[i].height; fival->discrete.numerator = 1; fival->discrete.denominator = res_table[i].fps; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_g_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *interval) { struct ap1302_device *dev = to_ap1302_device(sd); enum ap1302_contexts context; struct ap1302_res_struct *res_table; u32 cur_res; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); res_table = dev->cntx_res[context].res_table; cur_res = dev->cntx_res[context].cur_res; interval->interval.denominator = res_table[cur_res].fps; interval->interval.numerator = 1; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_frame_size_enum *fse) { struct ap1302_device *dev = to_ap1302_device(sd); enum ap1302_contexts context; struct ap1302_res_struct *res_table; int index = fse->index; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); if (index >= dev->cntx_res[context].res_num) { mutex_unlock(&dev->input_lock); return -EINVAL; } res_table = dev->cntx_res[context].res_table; fse->min_width = res_table[index].width; fse->min_height = res_table[index].height; fse->max_width = res_table[index].width; fse->max_height = res_table[index].height; mutex_unlock(&dev->input_lock); return 0; } static struct v4l2_mbus_framefmt * __ap1302_get_pad_format(struct ap1302_device *dev, struct v4l2_subdev_fh *fh, unsigned int pad, enum v4l2_subdev_format_whence which) { if (which == V4L2_SUBDEV_FORMAT_TRY) return v4l2_subdev_get_try_format(fh, pad); else return &dev->format; } static int ap1302_get_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_format *fmt) { struct ap1302_device *dev = to_ap1302_device(sd); struct v4l2_mbus_framefmt *format = __ap1302_get_pad_format(dev, fh, fmt->pad, fmt->which); mutex_lock(&dev->input_lock); fmt->format = *format; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, struct v4l2_subdev_format *fmt) { struct ap1302_device *dev = to_ap1302_device(sd); mutex_lock(&dev->input_lock); if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) dev->format = fmt->format; mutex_unlock(&dev->input_lock); return 0; } static int ap1302_g_skip_frames(struct v4l2_subdev *sd, u32 *frames) { *frames = 0; return 0; } static int ap1302_s_stream(struct v4l2_subdev *sd, int enable) { struct ap1302_device *dev = to_ap1302_device(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); enum ap1302_contexts context; u32 reg_val; int ret; mutex_lock(&dev->input_lock); context = ap1302_get_context(sd); dev_dbg(&client->dev, "ap1302_s_stream. context=%d enable=%d\n", context, enable); /* Switch context */ ap1302_i2c_read_reg(sd, REG_CTRL, AP1302_REG16, ®_val); reg_val &= ~CTRL_CNTX_MASK; reg_val |= (context<dev, "Start stream. context=%d\n", context); ap1302_dump_context_reg(sd, context); if (!dev->sys_activated) { reg_val = AP1302_SYS_ACTIVATE; dev->sys_activated = 1; } else { reg_val = AP1302_SYS_SWITCH; } } else { dev_info(&client->dev, "Stop stream. context=%d\n", context); reg_val = AP1302_SYS_SWITCH; } ret = ap1302_i2c_write_reg(sd, REG_SYS_START, AP1302_REG16, reg_val); if (ret) dev_err(&client->dev, "AP1302 set stream failed. enable=%d\n", enable); mutex_unlock(&dev->input_lock); return ret; } static u16 ap1302_ev_values[] = {0xfd00, 0xfe80, 0x0, 0x180, 0x300}; static int ap1302_set_exposure_off(struct v4l2_subdev *sd, s32 val) { val -= AP1302_MIN_EV; return ap1302_i2c_write_reg(sd, REG_AE_BV_OFF, AP1302_REG16, ap1302_ev_values[val]); } static u16 ap1302_wb_values[] = { 0, /* V4L2_WHITE_BALANCE_MANUAL */ 0xf, /* V4L2_WHITE_BALANCE_AUTO */ 0x2, /* V4L2_WHITE_BALANCE_INCANDESCENT */ 0x4, /* V4L2_WHITE_BALANCE_FLUORESCENT */ 0x5, /* V4L2_WHITE_BALANCE_FLUORESCENT_H */ 0x1, /* V4L2_WHITE_BALANCE_HORIZON */ 0x5, /* V4L2_WHITE_BALANCE_DAYLIGHT */ 0xf, /* V4L2_WHITE_BALANCE_FLASH */ 0x6, /* V4L2_WHITE_BALANCE_CLOUDY */ 0x6, /* V4L2_WHITE_BALANCE_SHADE */ }; static int ap1302_set_wb_mode(struct v4l2_subdev *sd, s32 val) { int ret = 0; u16 reg_val; ret = ap1302_i2c_read_reg(sd, REG_AWB_CTRL, AP1302_REG16, ®_val); if (ret) return ret; reg_val &= ~AWB_CTRL_MODE_MASK; reg_val |= ap1302_wb_values[val] << AWB_CTRL_MODE_OFFSET; if (val == V4L2_WHITE_BALANCE_FLASH) reg_val |= AWB_CTRL_FLASH_MASK; else reg_val &= ~AWB_CTRL_FLASH_MASK; ret = ap1302_i2c_write_reg(sd, REG_AWB_CTRL, AP1302_REG16, reg_val); return ret; } static int ap1302_set_zoom(struct v4l2_subdev *sd, s32 val) { ap1302_i2c_write_reg(sd, REG_DZ_TGT_FCT, AP1302_REG16, val * 4 + 0x100); return 0; } static u16 ap1302_sfx_values[] = { 0x00, /* V4L2_COLORFX_NONE */ 0x03, /* V4L2_COLORFX_BW */ 0x0d, /* V4L2_COLORFX_SEPIA */ 0x07, /* V4L2_COLORFX_NEGATIVE */ 0x04, /* V4L2_COLORFX_EMBOSS */ 0x0f, /* V4L2_COLORFX_SKETCH */ 0x08, /* V4L2_COLORFX_SKY_BLUE */ 0x09, /* V4L2_COLORFX_GRASS_GREEN */ 0x0a, /* V4L2_COLORFX_SKIN_WHITEN */ 0x00, /* V4L2_COLORFX_VIVID */ 0x00, /* V4L2_COLORFX_AQUA */ 0x00, /* V4L2_COLORFX_ART_FREEZE */ 0x00, /* V4L2_COLORFX_SILHOUETTE */ 0x10, /* V4L2_COLORFX_SOLARIZATION */ 0x02, /* V4L2_COLORFX_ANTIQUE */ 0x00, /* V4L2_COLORFX_SET_CBCR */ }; static int ap1302_set_special_effect(struct v4l2_subdev *sd, s32 val) { ap1302_i2c_write_reg(sd, REG_SFX_MODE, AP1302_REG16, ap1302_sfx_values[val]); return 0; } static u16 ap1302_scene_mode_values[] = { 0x00, /* V4L2_SCENE_MODE_NONE */ 0x07, /* V4L2_SCENE_MODE_BACKLIGHT */ 0x0a, /* V4L2_SCENE_MODE_BEACH_SNOW */ 0x06, /* V4L2_SCENE_MODE_CANDLE_LIGHT */ 0x00, /* V4L2_SCENE_MODE_DAWN_DUSK */ 0x00, /* V4L2_SCENE_MODE_FALL_COLORS */ 0x0d, /* V4L2_SCENE_MODE_FIREWORKS */ 0x02, /* V4L2_SCENE_MODE_LANDSCAPE */ 0x05, /* V4L2_SCENE_MODE_NIGHT */ 0x0c, /* V4L2_SCENE_MODE_PARTY_INDOOR */ 0x01, /* V4L2_SCENE_MODE_PORTRAIT */ 0x03, /* V4L2_SCENE_MODE_SPORTS */ 0x0e, /* V4L2_SCENE_MODE_SUNSET */ 0x0b, /* V4L2_SCENE_MODE_TEXT */ }; static int ap1302_set_scene_mode(struct v4l2_subdev *sd, s32 val) { ap1302_i2c_write_reg(sd, REG_SCENE_CTRL, AP1302_REG16, ap1302_scene_mode_values[val]); return 0; } static u16 ap1302_flicker_values[] = { 0x0, /* OFF */ 0x3201, /* 50HZ */ 0x3c01, /* 60HZ */ 0x2 /* AUTO */ }; static int ap1302_set_flicker_freq(struct v4l2_subdev *sd, s32 val) { ap1302_i2c_write_reg(sd, REG_FLICK_CTRL, AP1302_REG16, ap1302_flicker_values[val]); return 0; } static int ap1302_s_ctrl(struct v4l2_ctrl *ctrl) { struct ap1302_device *dev = container_of( ctrl->handler, struct ap1302_device, ctrl_handler); switch (ctrl->id) { case V4L2_CID_RUN_MODE: dev->cur_context = ap1302_cntx_mapping[ctrl->val]; break; case V4L2_CID_EXPOSURE: ap1302_set_exposure_off(&dev->sd, ctrl->val); break; case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: ap1302_set_wb_mode(&dev->sd, ctrl->val); break; case V4L2_CID_ZOOM_ABSOLUTE: ap1302_set_zoom(&dev->sd, ctrl->val); break; case V4L2_CID_COLORFX: ap1302_set_special_effect(&dev->sd, ctrl->val); break; case V4L2_CID_SCENE_MODE: ap1302_set_scene_mode(&dev->sd, ctrl->val); break; case V4L2_CID_POWER_LINE_FREQUENCY: ap1302_set_flicker_freq(&dev->sd, ctrl->val); break; default: return -EINVAL; } return 0; } static int ap1302_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) { struct ap1302_device *dev = to_ap1302_device(sd); int ret; u32 reg_val; if (reg->size != AP1302_REG16 && reg->size != AP1302_REG32) return -EINVAL; mutex_lock(&dev->input_lock); if (dev->power_on) ret = ap1302_i2c_read_reg(sd, reg->reg, reg->size, ®_val); else ret = -EIO; mutex_unlock(&dev->input_lock); if (ret) return ret; reg->val = reg_val; return 0; } static int ap1302_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) { struct ap1302_device *dev = to_ap1302_device(sd); int ret; if (reg->size != AP1302_REG16 && reg->size != AP1302_REG32) return -EINVAL; mutex_lock(&dev->input_lock); if (dev->power_on) ret = ap1302_i2c_write_reg(sd, reg->reg, reg->size, reg->val); else ret = -EIO; mutex_unlock(&dev->input_lock); return ret; } static long ap1302_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { long ret = 0; switch (cmd) { case VIDIOC_DBG_G_REGISTER: ret = ap1302_g_register(sd, arg); break; case VIDIOC_DBG_S_REGISTER: ret = ap1302_s_register(sd, arg); break; default: ret = -EINVAL; } return ret; } static const struct v4l2_ctrl_ops ctrl_ops = { .s_ctrl = ap1302_s_ctrl, }; static const char * const ctrl_run_mode_menu[] = { NULL, "Video", "Still capture", "Continuous capture", "Preview", }; static const struct v4l2_ctrl_config ctrls[] = { { .ops = &ctrl_ops, .id = V4L2_CID_RUN_MODE, .name = "Run Mode", .type = V4L2_CTRL_TYPE_MENU, .min = 1, .def = 4, .max = 4, .qmenu = ctrl_run_mode_menu, }, { .ops = &ctrl_ops, .id = V4L2_CID_EXPOSURE, .name = "Exposure", .type = V4L2_CTRL_TYPE_INTEGER, .min = AP1302_MIN_EV, .def = 0, .max = AP1302_MAX_EV, .step = 1, }, { .ops = &ctrl_ops, .id = V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, .name = "White Balance", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .def = 0, .max = 9, .step = 1, }, { .ops = &ctrl_ops, .id = V4L2_CID_ZOOM_ABSOLUTE, .name = "Zoom Absolute", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .def = 0, .max = 1024, .step = 1, }, { .ops = &ctrl_ops, .id = V4L2_CID_COLORFX, .name = "Color Special Effect", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .def = 0, .max = 15, .step = 1, }, { .ops = &ctrl_ops, .id = V4L2_CID_SCENE_MODE, .name = "Scene Mode", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .def = 0, .max = 13, .step = 1, }, { .ops = &ctrl_ops, .id = V4L2_CID_POWER_LINE_FREQUENCY, .name = "Light frequency filter", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .def = 3, .max = 3, .step = 1, }, }; static struct v4l2_subdev_sensor_ops ap1302_sensor_ops = { .g_skip_frames = ap1302_g_skip_frames, }; static const struct v4l2_subdev_video_ops ap1302_video_ops = { .try_mbus_fmt = ap1302_try_mbus_fmt, .s_mbus_fmt = ap1302_set_mbus_fmt, .g_mbus_fmt = ap1302_get_mbus_fmt, .s_stream = ap1302_s_stream, .enum_framesizes = ap1302_enum_framesizes, .enum_frameintervals = ap1302_enum_frameintervals, .g_frame_interval = ap1302_g_frame_interval, }; static const struct v4l2_subdev_core_ops ap1302_core_ops = { .s_power = ap1302_s_power, .queryctrl = v4l2_subdev_queryctrl, .g_ctrl = v4l2_subdev_g_ctrl, .s_ctrl = v4l2_subdev_s_ctrl, .ioctl = ap1302_ioctl, #ifdef CONFIG_VIDEO_ADV_DEBUG .g_register = ap1302_g_register, .s_register = ap1302_s_register, #endif }; static const struct v4l2_subdev_pad_ops ap1302_pad_ops = { .enum_mbus_code = ap1302_enum_mbus_code, .enum_frame_size = ap1302_enum_frame_size, .get_fmt = ap1302_get_pad_format, .set_fmt = ap1302_set_pad_format, }; static const struct v4l2_subdev_ops ap1302_ops = { .core = &ap1302_core_ops, .pad = &ap1302_pad_ops, .video = &ap1302_video_ops, .sensor = &ap1302_sensor_ops }; static int ap1302_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); struct ap1302_device *dev = to_ap1302_device(sd); if (dev->platform_data->platform_deinit) dev->platform_data->platform_deinit(); release_firmware(dev->fw); media_entity_cleanup(&dev->sd.entity); dev->platform_data->csi_cfg(sd, 0); v4l2_device_unregister_subdev(sd); return 0; } static int ap1302_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ap1302_device *dev; int ret; unsigned int i; dev_info(&client->dev, "ap1302 probe called.\n"); /* allocate device & init sub device */ dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { dev_err(&client->dev, "%s: out of memory\n", __func__); return -ENOMEM; } mutex_init(&dev->input_lock); v4l2_i2c_subdev_init(&(dev->sd), client, &ap1302_ops); ret = ap1302_request_firmware(&(dev->sd)); if (ret) { dev_err(&client->dev, "Cannot request ap1302 firmware.\n"); goto out_free; } dev->regmap16 = devm_regmap_init_i2c(client, &ap1302_reg16_config); if (IS_ERR(dev->regmap16)) { ret = PTR_ERR(dev->regmap16); dev_err(&client->dev, "Failed to allocate 16bit register map: %d\n", ret); return ret; } dev->regmap32 = devm_regmap_init_i2c(client, &ap1302_reg32_config); if (IS_ERR(dev->regmap32)) { ret = PTR_ERR(dev->regmap32); dev_err(&client->dev, "Failed to allocate 32bit register map: %d\n", ret); return ret; } if (client->dev.platform_data) { ret = ap1302_s_config(&dev->sd, client->dev.platform_data); if (ret) goto out_free; } dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; dev->pad.flags = MEDIA_PAD_FL_SOURCE; dev->sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; dev->cntx_res[CONTEXT_PREVIEW].res_num = ARRAY_SIZE(ap1302_preview_res); dev->cntx_res[CONTEXT_PREVIEW].res_table = ap1302_preview_res; dev->cntx_res[CONTEXT_SNAPSHOT].res_num = ARRAY_SIZE(ap1302_snapshot_res); dev->cntx_res[CONTEXT_SNAPSHOT].res_table = ap1302_snapshot_res; dev->cntx_res[CONTEXT_VIDEO].res_num = ARRAY_SIZE(ap1302_video_res); dev->cntx_res[CONTEXT_VIDEO].res_table = ap1302_video_res; ret = v4l2_ctrl_handler_init(&dev->ctrl_handler, ARRAY_SIZE(ctrls)); if (ret) { ap1302_remove(client); return ret; } for (i = 0; i < ARRAY_SIZE(ctrls); i++) v4l2_ctrl_new_custom(&dev->ctrl_handler, &ctrls[i], NULL); if (dev->ctrl_handler.error) { ap1302_remove(client); return dev->ctrl_handler.error; } /* Use same lock for controls as for everything else. */ dev->ctrl_handler.lock = &dev->input_lock; dev->sd.ctrl_handler = &dev->ctrl_handler; v4l2_ctrl_handler_setup(&dev->ctrl_handler); dev->run_mode = v4l2_ctrl_find(&dev->ctrl_handler, V4L2_CID_RUN_MODE); v4l2_ctrl_s_ctrl(dev->run_mode, ATOMISP_RUN_MODE_PREVIEW); ret = media_entity_init(&dev->sd.entity, 1, &dev->pad, 0); if (ret) ap1302_remove(client); return ret; out_free: v4l2_device_unregister_subdev(&dev->sd); return ret; } static const struct i2c_device_id ap1302_id[] = { {AP1302_NAME, 0}, {} }; MODULE_DEVICE_TABLE(i2c, ap1302_id); static struct i2c_driver ap1302_driver = { .driver = { .owner = THIS_MODULE, .name = AP1302_NAME, }, .probe = ap1302_probe, .remove = ap1302_remove, .id_table = ap1302_id, }; module_i2c_driver(ap1302_driver); MODULE_AUTHOR("Tianshu Qiu "); MODULE_DESCRIPTION("AP1302 Driver"); MODULE_LICENSE("GPL");