| /* |
| * Support for mipi CSI data generator. |
| * |
| * Copyright (c) 2014 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 <asm/div64.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| |
| #include "pixter.h" |
| |
| #define to_pixter_dev(sd) container_of(sd, struct pixter_device, sd) |
| #define dev_off(m) offsetof(struct pixter_device, m) |
| |
| static struct regmap_config pixter_reg_config = { |
| .reg_bits = 8, |
| .val_bits = 32, |
| .val_format_endian = REGMAP_ENDIAN_NATIVE, |
| }; |
| |
| static struct pixter_format_bridge format_bridge[] = { |
| {"", 0, ATOMISP_INPUT_FORMAT_BINARY_8, 8}, |
| {"RGGB10", V4L2_MBUS_FMT_SRGGB10_1X10, ATOMISP_INPUT_FORMAT_RAW_10, 10}, |
| {"GRBG10", V4L2_MBUS_FMT_SGRBG10_1X10, ATOMISP_INPUT_FORMAT_RAW_10, 10}, |
| {"GBRG10", V4L2_MBUS_FMT_SGBRG10_1X10, ATOMISP_INPUT_FORMAT_RAW_10, 10}, |
| {"BGGR10", V4L2_MBUS_FMT_SBGGR10_1X10, ATOMISP_INPUT_FORMAT_RAW_10, 10}, |
| {"RGGB8", V4L2_MBUS_FMT_SRGGB8_1X8, ATOMISP_INPUT_FORMAT_RAW_8, 8}, |
| {"GRBG8", V4L2_MBUS_FMT_SGRBG8_1X8, ATOMISP_INPUT_FORMAT_RAW_8, 8}, |
| {"GBRG8", V4L2_MBUS_FMT_SGBRG8_1X8, ATOMISP_INPUT_FORMAT_RAW_8, 8}, |
| {"BGGR8", V4L2_MBUS_FMT_SBGGR8_1X8, ATOMISP_INPUT_FORMAT_RAW_8, 8}, |
| {"YUV422_8", V4L2_MBUS_FMT_UYVY8_1X16, ATOMISP_INPUT_FORMAT_YUV422_8, 16}, |
| {"YUV420_8", 0x8001/*For YUV420*/, ATOMISP_INPUT_FORMAT_YUV420_8, 16}, |
| }; |
| |
| static u32 port_to_channel[4] = {1, 0, 2, 0}; |
| |
| static struct pixter_dbgfs dbgfs[] = { |
| {"root", NULL, DBGFS_DIR, 0, 0}, |
| {"fps", "root", DBGFS_DIR, 0, 0}, |
| {"blank", "root", DBGFS_DIR, 0, 0}, |
| {"timing", "root", DBGFS_DIR, 0, 0}, |
| {"fps_ovrd", "fps", DBGFS_FILE, PIXTER_RW, dev_off(dbg_fps.fps_ovrd)}, |
| {"fps", "fps", DBGFS_FILE, PIXTER_RW, dev_off(dbg_fps.fps)}, |
| {"blank_ovrd", "blank", DBGFS_FILE, PIXTER_RW, dev_off(dbg_blank.blank_ovrd)}, |
| {"h_blank", "blank", DBGFS_FILE, PIXTER_RW, dev_off(dbg_blank.h_blank)}, |
| {"v_blank_pre", "blank", DBGFS_FILE, PIXTER_RW, dev_off(dbg_blank.v_blank_pre)}, |
| {"v_blank_post", "blank", DBGFS_FILE, PIXTER_RW, dev_off(dbg_blank.v_blank_post)}, |
| {"mipi_clk", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.mipi_clk)}, |
| {"cont_hs_clk", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.cont_hs_clk)}, |
| {"timing_ovrd", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.timing_ovrd)}, |
| {"pre", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.pre)}, |
| {"post", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.post)}, |
| {"gap", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.gap)}, |
| {"ck_lpx", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.ck_lpx)}, |
| {"ck_prep", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.ck_prep)}, |
| {"ck_zero", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.ck_zero)}, |
| {"ck_trail", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.ck_trail)}, |
| {"dat_lpx", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.dat_lpx)}, |
| {"dat_prep", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.dat_prep)}, |
| {"dat_zero", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.dat_zero)}, |
| {"dat_trail", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.dat_trail)}, |
| {"twakeup", "timing", DBGFS_FILE, PIXTER_RW, dev_off(dbg_timing.twakeup)}, |
| {"mipi_lanes_num", "timing", DBGFS_FILE, PIXTER_RONLY, dev_off(dbg_timing.mipi_lanes_num)}, |
| }; |
| |
| static u32 pixter_get_tx_freq_sel(u32 *freq) |
| { |
| u32 sel; |
| |
| *freq /= 1000000; /* To MHz */ |
| if (*freq < 20) { |
| sel = 1; |
| *freq = 20; |
| } else if (*freq <= 100) { |
| sel = (*freq + 9) / 10 - 1; |
| *freq = 20 + (sel - 1) * 10; |
| } else if (*freq <= 750) { |
| sel = (*freq + 24) / 25 + 5; |
| *freq = 100 + (sel - 9) * 25; |
| } else { |
| sel = 35; |
| *freq = 750; |
| } |
| *freq *= 1000000; |
| |
| return sel; |
| } |
| |
| static int pixter_read_reg(struct v4l2_subdev *sd, u32 reg, u32 *val) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret; |
| |
| if (!dev->regmap) |
| return -EIO; |
| |
| ret = regmap_write(dev->regmap, PIXTER_I2C_ADDR, reg | 1); |
| ret |= regmap_read(dev->regmap, PIXTER_I2C_DATA_R, val); |
| if (ret) { |
| dev_dbg(&client->dev, "Read reg failed. reg=0x%04X\n", reg); |
| return ret; |
| } |
| dev_dbg(&client->dev, "read_reg[0x%04X] = 0x%08X\n", |
| reg, *val); |
| return ret; |
| } |
| |
| static int pixter_write_reg(struct v4l2_subdev *sd, u32 reg, u32 val) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret; |
| |
| if (!dev->regmap) |
| return -EIO; |
| |
| ret = regmap_write(dev->regmap, PIXTER_I2C_DATA_W, val); |
| ret |= regmap_write(dev->regmap, PIXTER_I2C_ADDR, reg); |
| if (ret) { |
| dev_dbg(&client->dev, "Write reg failed. reg=0x%04X\n", reg); |
| return ret; |
| } |
| dev_dbg(&client->dev, "write_reg[0x%04X] = 0x%08X\n", |
| reg, (u32)val); |
| return ret; |
| } |
| |
| static int pixter_read_buf(struct v4l2_subdev *sd, |
| u32 addr, u32 size, void *buf) |
| { |
| u32 i; |
| int ret = 0; |
| |
| for (i = 0; i < size; i += 4) { |
| ret = pixter_read_reg(sd, addr + i, (u32*)((u8*)buf + i)); |
| if (ret) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int pixter_read_mipi_timing(struct v4l2_subdev *sd) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| u32 reg_val, ch; |
| ch = port_to_channel[dev->mipi_info->port]; |
| pixter_read_reg(sd, PIXTER_TX_CTRL_TIMING(ch), ®_val); |
| dev->dbg_timing.pre = reg_val & 0x7F; |
| dev->dbg_timing.post = (reg_val >> 8) & 0x7F; |
| dev->dbg_timing.gap = (reg_val >> 16) & 0x7F; |
| pixter_read_reg(sd, PIXTER_TX_CK_TIMING(ch), ®_val); |
| dev->dbg_timing.ck_lpx = reg_val & 0x7F; |
| dev->dbg_timing.ck_prep = (reg_val >> 8) & 0x7F; |
| dev->dbg_timing.ck_zero = (reg_val >> 16) & 0x7F; |
| dev->dbg_timing.ck_trail = (reg_val >> 24) & 0x7F; |
| pixter_read_reg(sd, PIXTER_TX_DAT_TIMING(ch), ®_val); |
| dev->dbg_timing.dat_lpx = reg_val & 0x7F; |
| dev->dbg_timing.dat_prep = (reg_val >> 8) & 0x7F; |
| dev->dbg_timing.dat_zero = (reg_val >> 16) & 0x7F; |
| dev->dbg_timing.dat_trail = (reg_val >> 24) & 0x7F; |
| pixter_read_reg(sd, PIXTER_TX_ULPS_TIMING(ch), ®_val); |
| dev->dbg_timing.twakeup = reg_val & 0x7FFFF; |
| return 0; |
| } |
| |
| static int pixter_config_rx(struct v4l2_subdev *sd) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct pixter_setting *setting; |
| u32 h_blank, v_blank_pre, v_blank_post; |
| u32 i, reg_val, ch, vc = 0; |
| u32 line_interval; |
| u32 width_bits; |
| u32 bit_rate; |
| u32 line_bits; |
| |
| setting = &dev->settings[dev->cur_setting]; |
| ch = port_to_channel[dev->mipi_info->port]; |
| |
| /* Set setting start and end address in DDR SDRAM. */ |
| pixter_write_reg(sd, PIXTER_RDR_START(ch), setting->start); |
| pixter_write_reg(sd, PIXTER_RDR_END(ch), setting->end); |
| |
| /* Set FPS. */ |
| if (dev->dbg_fps.fps_ovrd) |
| reg_val = dev->dbg_fps.fps << 24; |
| else |
| reg_val = setting->vc[setting->def_vc].fps << 24; |
| |
| if (setting->block_mode) |
| reg_val |= PIXTER_DFT_BLOCK_MODE; |
| |
| if (dev->caps->sensor[0].stream_num > 1) { |
| /* Select the channel with the largest width. */ |
| for (i = 1; i < 4; i++) { |
| if (setting->vc[i].width > setting->vc[vc].width) |
| vc = i; |
| } |
| } |
| width_bits = setting->vc[vc].width * |
| format_bridge[setting->vc[vc].format].bpp; |
| bit_rate = dev->dbg_timing.mipi_clk / 1000000 * 2 * |
| dev->mipi_info->num_lanes; |
| if (bit_rate == 0 || bit_rate > PIXTER_MAX_BITRATE_MBPS) { |
| dev_err(&client->dev, "Invalid bit rate %dMbps.\n", bit_rate); |
| return -EINVAL; |
| } |
| |
| if (dev->dbg_blank.blank_ovrd) { |
| h_blank = dev->dbg_blank.h_blank; |
| line_bits = 1000 * (width_bits + h_blank * |
| format_bridge[setting->vc[vc].format].bpp); |
| line_interval = line_bits / bit_rate; |
| v_blank_pre = dev->dbg_blank.v_blank_pre; |
| v_blank_post = dev->dbg_blank.v_blank_post; |
| } else { |
| line_bits = 1200 * width_bits; |
| line_interval = line_bits / bit_rate; |
| v_blank_pre = PIXTER_DEF_VBPRE; |
| v_blank_post = line_interval; |
| } |
| |
| /* Set line interval */ |
| reg_val |= (line_interval / 8) << 8; |
| pixter_write_reg(sd, PIXTER_DFT_CTRL(ch), reg_val); |
| |
| /* Set vertical blanking */ |
| reg_val = ((v_blank_post / 8) << 16) + v_blank_pre / 8; |
| pixter_write_reg(sd, PIXTER_VERT_BLANK(ch), reg_val); |
| |
| return 0; |
| } |
| |
| static int pixter_config_tx(struct v4l2_subdev *sd) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| u32 reg_val, ch; |
| u32 cnt = 500; |
| |
| /* Set the parameters in the tx_csi2_ctrl. */ |
| ch = port_to_channel[dev->mipi_info->port]; |
| reg_val = dev->mipi_info->num_lanes - 1; |
| reg_val |= (dev->dbg_timing.cont_hs_clk << 4); |
| if (!dev->dbg_timing.timing_ovrd) |
| reg_val |= 1 << 8; |
| pixter_write_reg(sd, PIXTER_TX_CSI2_CTRL(ch), reg_val); |
| |
| /* Set MIPI timing if overided. */ |
| if (dev->dbg_timing.timing_ovrd) { |
| reg_val = dev->dbg_timing.pre; |
| reg_val |= dev->dbg_timing.post << 8; |
| reg_val |= dev->dbg_timing.gap << 16; |
| pixter_write_reg(sd, PIXTER_TX_CTRL_TIMING(ch), reg_val); |
| reg_val = dev->dbg_timing.ck_lpx; |
| reg_val |= dev->dbg_timing.ck_prep << 8; |
| reg_val |= dev->dbg_timing.ck_zero << 16; |
| reg_val |= dev->dbg_timing.ck_trail << 24; |
| pixter_write_reg(sd, PIXTER_TX_CK_TIMING(ch), reg_val); |
| reg_val = dev->dbg_timing.dat_lpx; |
| reg_val |= dev->dbg_timing.dat_prep << 8; |
| reg_val |= dev->dbg_timing.dat_zero << 16; |
| reg_val |= dev->dbg_timing.dat_trail << 24; |
| pixter_write_reg(sd, PIXTER_TX_DAT_TIMING(ch), reg_val); |
| reg_val = dev->dbg_timing.twakeup; |
| pixter_write_reg(sd, PIXTER_TX_ULPS_TIMING(ch), reg_val); |
| } |
| |
| /* Config MIPI clock. */ |
| reg_val = pixter_get_tx_freq_sel(&dev->dbg_timing.mipi_clk); |
| pixter_write_reg(sd, PIXTER_TX_CTRL(ch), reg_val); |
| /* Wait MIPI clock to be ready. Timeout=5s. */ |
| while (cnt) { |
| pixter_write_reg(sd, PIXTER_TX_CTRL_NNS(ch), 1); |
| pixter_read_reg(sd, PIXTER_TX_STATUS(ch), ®_val); |
| if (reg_val & PIXTER_TX_READY) |
| break; |
| usleep_range(10000, 10000); |
| cnt--; |
| } |
| if (cnt == 0) { |
| dev_err(&client->dev, "Wait MIPI clock ready timeout.\n"); |
| return -EBUSY; |
| } |
| pixter_read_mipi_timing(sd); |
| |
| return 0; |
| } |
| |
| static void __print_mipi_timing(struct v4l2_subdev *sd) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct pixter_timing *dbg_time = &dev->dbg_timing; |
| unsigned short mipi_clk = dbg_time->mipi_clk / 1000000; |
| /* 1UI = 1 bit periold, in pS */ |
| unsigned int ui = 1000000 / (mipi_clk * 2); |
| unsigned int tmp; |
| |
| dev_dbg(&client->dev, "MIPI CLK: %d MHz.\n", mipi_clk); |
| |
| dev_dbg(&client->dev, "----Pixter MIPI Parameters----\n"); |
| dev_dbg(&client->dev, "ck_lpx: %d.\n", dbg_time->ck_lpx); |
| dev_dbg(&client->dev, "ck_prep: %d.\n", dbg_time->ck_prep); |
| dev_dbg(&client->dev, "ck_zero: %d.\n", dbg_time->ck_zero); |
| dev_dbg(&client->dev, "pre: %d.\n", dbg_time->pre); |
| dev_dbg(&client->dev, "post: %d.\n", dbg_time->post); |
| dev_dbg(&client->dev, "ck_trail: %d.\n", dbg_time->ck_trail); |
| dev_dbg(&client->dev, "dat_lpx: %d.\n", dbg_time->dat_lpx); |
| dev_dbg(&client->dev, "dat_prep: %d.\n", dbg_time->dat_prep); |
| dev_dbg(&client->dev, "dat_zero: %d.\n", dbg_time->dat_zero); |
| dev_dbg(&client->dev, "dat_trail: %d.\n", dbg_time->dat_trail); |
| dev_dbg(&client->dev, "gap: %d.\n", dbg_time->gap); |
| dev_dbg(&client->dev, "twakeup: %d.\n", dbg_time->twakeup); |
| |
| dev_dbg(&client->dev, "----Standard MIPI Parameters----\n"); |
| |
| tmp = (dbg_time->ck_lpx + 1) * 8 * ui; |
| dev_dbg(&client->dev, "CLK-LPX: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->ck_prep + 1) * 8 * ui; |
| dev_dbg(&client->dev, "CLK-PREPARE: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->ck_zero + 1) * 8 * ui; |
| dev_dbg(&client->dev, "CLK-ZERO: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->pre - dbg_time->ck_lpx - dbg_time->ck_zero - 3) |
| * 8 * ui; |
| dev_dbg(&client->dev, "CLK-PRE: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->post + 8) * 8 * ui; |
| dev_dbg(&client->dev, "CLK-POST: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->ck_trail + 1) * 8 * ui; |
| dev_dbg(&client->dev, "CLK-TRAIL: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->dat_lpx + 1) * 8 * ui; |
| dev_dbg(&client->dev, "HS-LPX: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->dat_prep + 1) * 8 * ui; |
| dev_dbg(&client->dev, "HS-PREPARE: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->dat_zero + 6) * 8 * ui; |
| dev_dbg(&client->dev, "HS-ZERO: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->dat_trail + 2) * 8 * ui; |
| dev_dbg(&client->dev, "HS-TRAIL: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->gap + 9) * 8 * ui; |
| dev_dbg(&client->dev, "HS-EXIT: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| tmp = (dbg_time->twakeup + 1) * 8 * ui; |
| dev_dbg(&client->dev, "Wakeup: %d.%d nS.\n", |
| tmp / 1000, tmp % 1000); |
| |
| } |
| |
| static int pixter_s_stream(struct v4l2_subdev *sd, int enable) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| int ret = 0; |
| u32 reg_val; |
| |
| dev_dbg(&client->dev, "Set stream for pixter. enable=%d\n", enable); |
| |
| mutex_lock(&dev->input_lock); |
| |
| if (enable) { |
| __print_mipi_timing(sd); |
| ret = pixter_config_rx(sd); |
| if (ret) |
| goto out; |
| ret = pixter_config_tx(sd); |
| if (ret) |
| goto out; |
| } |
| |
| /* Enable stream output. */ |
| reg_val = 1 << port_to_channel[dev->mipi_info->port]; |
| if (!enable) |
| reg_val <<= 4; |
| pixter_write_reg(sd, PIXTER_CPX_CTRL, reg_val); |
| |
| if (!enable) |
| memset(dev->vc_setting, 0, sizeof(dev->vc_setting)); |
| out: |
| mutex_unlock(&dev->input_lock); |
| |
| return ret; |
| } |
| |
| static int pixter_g_frame_interval(struct v4l2_subdev *sd, |
| struct v4l2_subdev_frame_interval *interval) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct pixter_setting *setting; |
| |
| mutex_lock(&dev->input_lock); |
| setting = &dev->settings[dev->cur_setting]; |
| |
| /* Return the currently selected settings' maximum frame interval */ |
| |
| interval->interval.numerator = 1; |
| if (dev->dbg_fps.fps_ovrd) |
| interval->interval.denominator = dev->dbg_fps.fps; |
| else |
| interval->interval.denominator = |
| setting->vc[setting->def_vc].fps; |
| |
| mutex_unlock(&dev->input_lock); |
| |
| return 0; |
| } |
| |
| static int pixter_s_frame_interval(struct v4l2_subdev *sd, |
| struct v4l2_subdev_frame_interval *interval) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct pixter_setting *setting; |
| |
| if (interval->interval.numerator == 0) |
| return -EINVAL; |
| |
| mutex_lock(&dev->input_lock); |
| setting = &dev->settings[dev->cur_setting]; |
| setting->vc[setting->def_vc].fps = interval->interval.denominator / |
| interval->interval.numerator; |
| mutex_unlock(&dev->input_lock); |
| |
| return 0; |
| } |
| |
| static int pixter_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, |
| enum v4l2_mbus_pixelcode *code) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct pixter_setting *setting = &dev->settings[dev->cur_setting]; |
| |
| *code = format_bridge[setting->vc[0].format].v4l2_format; |
| |
| return 0; |
| } |
| |
| static u32 pixter_try_mbus_fmt_locked(struct v4l2_subdev *sd, |
| struct v4l2_mbus_framefmt *fmt) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct atomisp_input_stream_info *stream_info = |
| (struct atomisp_input_stream_info*)fmt->reserved; |
| struct pixter_setting *settings = dev->settings; |
| struct pixter_vc_setting *vc_setting = dev->vc_setting; |
| u32 vc, i, j; |
| s32 idx = -1, max_idx = -1; |
| s64 w0, h0, mismatch, distance; |
| s64 w1 = fmt->width; |
| s64 h1 = fmt->height; |
| s64 min_distance = LLONG_MAX; |
| |
| dev_dbg(&client->dev, "pixter_try_mbus_fmt. size=%dx%d stream=%d\n", |
| fmt->width, fmt->height, stream_info->stream); |
| if (dev->caps->sensor[0].stream_num == 1) |
| vc = 0; |
| else |
| vc = stream_info->stream; |
| for (i = 0; i < dev->setting_num; i++) { |
| if (dev->setting_en[i] == 0) |
| continue; |
| max_idx = i; |
| for (j = 0; j < 4; j++) { |
| if (!vc_setting[j].width) |
| continue; |
| if (vc_setting[j].width != settings[i].vc[j].width || |
| vc_setting[j].height != settings[i].vc[j].height) |
| break; |
| } |
| if (j < 4) |
| continue; |
| w0 = settings[i].vc[vc].width; |
| h0 = settings[i].vc[vc].height; |
| if (w0 < w1 || h0 < h1) |
| continue; |
| mismatch = abs(w0 * h1 - w1 * h0) * 8192; |
| do_div(mismatch, w1 * h0); |
| if (mismatch > 8192 * PIXTER_MAX_RATIO_MISMATCH / 100) |
| continue; |
| distance = (w0 * h1 + w1 * h0) * 8192; |
| do_div(distance, w1 * h1); |
| if (distance < min_distance) { |
| min_distance = distance; |
| idx = i; |
| } |
| } |
| if (idx < 0 && max_idx < 0) { |
| idx = dev->setting_num - 1; |
| dev_warn(&client->dev, "All settings disabled, using: %dx%d\n", |
| settings[idx].vc[vc].width, |
| settings[idx].vc[vc].height); |
| } else if (idx < 0) { |
| idx = max_idx; |
| dev_warn(&client->dev, "using max enabled resolution: %dx%d\n", |
| settings[idx].vc[vc].width, |
| settings[idx].vc[vc].height); |
| } |
| fmt->width = settings[idx].vc[vc].width; |
| fmt->height = settings[idx].vc[vc].height; |
| fmt->code = format_bridge[settings[idx].vc[vc].format].v4l2_format; |
| return idx; |
| } |
| |
| static int pixter_try_mbus_fmt(struct v4l2_subdev *sd, |
| struct v4l2_mbus_framefmt *fmt) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| |
| mutex_lock(&dev->input_lock); |
| pixter_try_mbus_fmt_locked(sd, fmt); |
| mutex_unlock(&dev->input_lock); |
| |
| return 0; |
| } |
| |
| static int pixter_g_mbus_fmt(struct v4l2_subdev *sd, |
| struct v4l2_mbus_framefmt *fmt) |
| { |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct atomisp_input_stream_info *stream_info = |
| (struct atomisp_input_stream_info*)fmt->reserved; |
| struct pixter_setting *setting; |
| u32 vc; |
| |
| if (!fmt) |
| return -EINVAL; |
| |
| mutex_lock(&dev->input_lock); |
| if (dev->caps->sensor[0].stream_num == 1) |
| vc = 0; |
| else |
| vc = stream_info->stream; |
| |
| setting = &dev->settings[dev->cur_setting]; |
| fmt->width = setting->vc[vc].width; |
| fmt->height = setting->vc[vc].height; |
| fmt->code = format_bridge[setting->vc[vc].format].v4l2_format; |
| mutex_unlock(&dev->input_lock); |
| |
| dev_dbg(&client->dev, "%s w:%d h:%d code: 0x%x stream: %d\n", __func__, |
| fmt->width, fmt->height, fmt->code, |
| stream_info->stream); |
| |
| return 0; |
| } |
| |
| static int pixter_s_mbus_fmt(struct v4l2_subdev *sd, |
| struct v4l2_mbus_framefmt *fmt) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct atomisp_input_stream_info *stream_info = |
| (struct atomisp_input_stream_info*)fmt->reserved; |
| |
| if (!fmt) |
| return -EINVAL; |
| |
| mutex_lock(&dev->input_lock); |
| dev->cur_setting = pixter_try_mbus_fmt_locked(sd, fmt); |
| if (dev->caps->sensor[0].stream_num == 1) |
| stream_info->ch_id = 0; |
| else |
| stream_info->ch_id = stream_info->stream; |
| dev->vc_setting[stream_info->ch_id] = |
| dev->settings[dev->cur_setting].vc[stream_info->ch_id]; |
| mutex_unlock(&dev->input_lock); |
| dev_dbg(&client->dev, "%s w:%d h:%d code: 0x%x stream: %d\n", __func__, |
| fmt->width, fmt->height, fmt->code, |
| stream_info->stream); |
| |
| return 0; |
| } |
| |
| static int pixter_g_register(struct v4l2_subdev *sd, |
| struct v4l2_dbg_register *reg) |
| { |
| int ret; |
| u32 reg_val; |
| |
| if (reg->size != 4) |
| return -EINVAL; |
| |
| ret = pixter_read_reg(sd, reg->reg, ®_val); |
| if (ret) |
| return ret; |
| |
| reg->val = reg_val; |
| |
| return 0; |
| } |
| |
| static int pixter_s_register(struct v4l2_subdev *sd, |
| const struct v4l2_dbg_register *reg) |
| { |
| if (reg->size != 4) |
| return -EINVAL; |
| |
| return pixter_write_reg(sd, reg->reg, reg->val); |
| } |
| |
| static long pixter_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) |
| { |
| switch (cmd) { |
| case ATOMISP_IOC_S_EXPOSURE: |
| break; |
| case ATOMISP_IOC_G_SENSOR_PRIV_INT_DATA: |
| break; |
| case VIDIOC_DBG_G_REGISTER: |
| pixter_g_register(sd, arg); |
| break; |
| case VIDIOC_DBG_S_REGISTER: |
| pixter_s_register(sd, arg); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int |
| pixter_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, |
| struct v4l2_subdev_mbus_code_enum *code) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| if (code->index >= 1) |
| return -EINVAL; |
| |
| mutex_lock(&dev->input_lock); |
| code->code = dev->format.code; |
| mutex_unlock(&dev->input_lock); |
| return 0; |
| } |
| |
| static int |
| pixter_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, |
| struct v4l2_subdev_frame_size_enum *fse) |
| { |
| int index = fse->index; |
| struct pixter_device *dev = to_pixter_dev(sd); |
| |
| mutex_lock(&dev->input_lock); |
| if (index >= 1) { |
| mutex_unlock(&dev->input_lock); |
| return -EINVAL; |
| } |
| |
| fse->min_width = dev->settings[dev->cur_setting].vc[0].width; |
| fse->min_height = dev->settings[dev->cur_setting].vc[0].height; |
| fse->max_width = fse->min_width; |
| fse->max_height = fse->min_height; |
| mutex_unlock(&dev->input_lock); |
| |
| return 0; |
| } |
| |
| static struct v4l2_mbus_framefmt * |
| __pixter_get_pad_format(struct pixter_device *sensor, |
| struct v4l2_subdev_fh *fh, unsigned int pad, |
| enum v4l2_subdev_format_whence which) |
| { |
| switch (which) { |
| case V4L2_SUBDEV_FORMAT_TRY: |
| return v4l2_subdev_get_try_format(fh, pad); |
| case V4L2_SUBDEV_FORMAT_ACTIVE: |
| return &sensor->format; |
| default: |
| return NULL; |
| } |
| } |
| |
| static int |
| pixter_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, |
| struct v4l2_subdev_format *fmt) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| |
| if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) |
| dev->format = fmt->format; |
| |
| return 0; |
| } |
| |
| static int |
| pixter_get_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, |
| struct v4l2_subdev_format *fmt) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct v4l2_mbus_framefmt *format = |
| __pixter_get_pad_format(dev, fh, fmt->pad, fmt->which); |
| |
| fmt->format = *format; |
| |
| return 0; |
| } |
| |
| static int pixter_enum_framesizes(struct v4l2_subdev *sd, |
| struct v4l2_frmsizeenum *fsize) |
| { |
| unsigned int index = fsize->index; |
| struct pixter_device *dev = to_pixter_dev(sd); |
| |
| mutex_lock(&dev->input_lock); |
| if (index >= 1) { |
| mutex_unlock(&dev->input_lock); |
| return -EINVAL; |
| } |
| |
| fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; |
| fsize->discrete.width = dev->settings[dev->cur_setting].vc[0].width; |
| fsize->discrete.height = dev->settings[dev->cur_setting].vc[0].height; |
| fsize->reserved[0] = 1; |
| mutex_unlock(&dev->input_lock); |
| |
| return 0; |
| } |
| |
| static int pixter_enum_frameintervals(struct v4l2_subdev *sd, |
| struct v4l2_frmivalenum *fival) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| |
| mutex_lock(&dev->input_lock); |
| fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; |
| fival->width = dev->settings[dev->cur_setting].vc[0].width; |
| fival->height = dev->settings[dev->cur_setting].vc[0].height; |
| fival->discrete.numerator = 1; |
| fival->discrete.denominator = 30; |
| mutex_unlock(&dev->input_lock); |
| |
| return 0; |
| } |
| |
| static int pixter_s_power(struct v4l2_subdev *sd, int on) |
| { |
| struct pixter_device *dev = to_pixter_dev(sd); |
| struct i2c_client *client = v4l2_get_subdevdata(sd); |
| u32 reg_val; |
| |
| dev_dbg(&client->dev, "Set power for pixter. on=%d\n", on); |
| /* Disable channel output. */ |
| reg_val = 1 << (port_to_channel[dev->mipi_info->port] + 4); |
| pixter_write_reg(sd, PIXTER_CPX_CTRL, reg_val); |
| memset(dev->vc_setting, 0, sizeof(dev->vc_setting)); |
| return 0; |
| } |
| |
| static ssize_t pixter_dbgfs_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) |
| { |
| struct pixter_dbgfs_data *data = file->f_inode->i_private; |
| struct pixter_device *dev = data->dev; |
| ssize_t ret = 0; |
| u32 *val = (u32*) data->ptr; |
| u32 i; |
| |
| char *str = kzalloc(1024, GFP_KERNEL); |
| if (!str) |
| return 0; |
| if (val >= dev->setting_en && |
| val < &dev->setting_en[dev->setting_num]) { |
| u32 idx = (val - dev->setting_en); |
| struct pixter_setting *setting = &dev->settings[idx]; |
| char sub_str[128]; |
| if (idx >= dev->setting_num) |
| goto out; |
| snprintf(str, 1024, "Valid VCs: %d\n", setting->valid_vc_num); |
| for (i = 0; i < 4; i++) { |
| struct pixter_vc_setting *vc = |
| &setting->vc[i]; |
| if (vc->width == 0) |
| continue; |
| snprintf(sub_str, 128, "VC%d: %dx%d @ %dfps - %s\n", |
| i, vc->width, vc->height, vc->fps, |
| format_bridge[vc->format].name); |
| strncat(str, sub_str, 1023 - strlen(str)); |
| } |
| snprintf(sub_str, 128, "Def: VC%d\nState: %s\n", |
| setting->def_vc, |
| dev->setting_en[idx] ? "Enabled" : "Disabled"); |
| strncat(str, sub_str, 1023 - strlen(str)); |
| } else { |
| snprintf(str, 1024, "%d\n", *val); |
| } |
| ret = simple_read_from_buffer(buf, size, ppos, str, strlen(str)); |
| out: |
| kfree(str); |
| return ret; |
| } |
| |
| static ssize_t pixter_dbgfs_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) |
| { |
| struct pixter_dbgfs_data *data = file->f_inode->i_private; |
| struct pixter_device *dev = data->dev; |
| u32 *val = (u32*) data->ptr; |
| char str[16] = {0}; |
| ssize_t ret; |
| |
| ret = simple_write_to_buffer(str, 16, ppos, buf, size); |
| sscanf(str, "%d", val); |
| if (val == &dev->dbg_timing.timing_ovrd && *val == 0) |
| pixter_config_tx(&dev->sd); |
| |
| return ret; |
| } |
| |
| static const char * const ctrl_run_mode_menu[] = { |
| NULL, |
| "Video", |
| "Still capture", |
| "Continuous capture", |
| "Preview", |
| }; |
| |
| static const struct v4l2_ctrl_config ctrls[] = { |
| { |
| .id = V4L2_CID_RUN_MODE, |
| .name = "Run Mode", |
| .type = V4L2_CTRL_TYPE_MENU, |
| .min = 1, |
| .def = 4, |
| .max = 4, |
| .qmenu = ctrl_run_mode_menu, |
| } |
| }; |
| |
| static const struct v4l2_subdev_core_ops pixter_core_ops = { |
| .s_power = pixter_s_power, |
| .queryctrl = v4l2_subdev_queryctrl, |
| .g_ctrl = v4l2_subdev_g_ctrl, |
| .s_ctrl = v4l2_subdev_s_ctrl, |
| .ioctl = pixter_ioctl, |
| #ifdef CONFIG_VIDEO_ADV_DEBUG |
| .g_register = pixter_g_register, |
| .s_register = pixter_s_register, |
| #endif |
| }; |
| |
| static const struct v4l2_subdev_video_ops pixter_video_ops = { |
| .s_stream = pixter_s_stream, |
| .enum_framesizes = pixter_enum_framesizes, |
| .enum_frameintervals = pixter_enum_frameintervals, |
| .enum_mbus_fmt = pixter_enum_mbus_fmt, |
| .try_mbus_fmt = pixter_try_mbus_fmt, |
| .g_mbus_fmt = pixter_g_mbus_fmt, |
| .s_mbus_fmt = pixter_s_mbus_fmt, |
| .g_frame_interval = pixter_g_frame_interval, |
| .s_frame_interval = pixter_s_frame_interval, |
| }; |
| |
| static const struct v4l2_subdev_pad_ops pixter_pad_ops = { |
| .enum_mbus_code = pixter_enum_mbus_code, |
| .enum_frame_size = pixter_enum_frame_size, |
| .get_fmt = pixter_get_pad_format, |
| .set_fmt = pixter_set_pad_format, |
| }; |
| |
| static const struct v4l2_subdev_ops pixter_ops = { |
| .core = &pixter_core_ops, |
| .video = &pixter_video_ops, |
| .pad = &pixter_pad_ops, |
| }; |
| |
| static const struct media_entity_operations pixter_entity_ops = { |
| .link_setup = NULL, |
| }; |
| |
| static const struct file_operations pixter_dbgfs_fops = { |
| .read = pixter_dbgfs_read, |
| .write = pixter_dbgfs_write, |
| .llseek = generic_file_llseek, |
| }; |
| |
| static int pixter_remove(struct i2c_client *client) |
| { |
| struct v4l2_subdev *sd = i2c_get_clientdata(client); |
| struct pixter_device *dev = to_pixter_dev(sd); |
| |
| if (dev->sd.entity.links) |
| media_entity_cleanup(&dev->sd.entity); |
| dev->platform_data->csi_cfg(sd, 0); |
| v4l2_device_unregister_subdev(sd); |
| if (dev->dbgfs_data) |
| debugfs_remove_recursive(dev->dbgfs_data[0].entry); |
| |
| return 0; |
| } |
| |
| static int pixter_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct pixter_device *dev; |
| const struct atomisp_camera_caps *caps = NULL; |
| char *pixter_name = NULL; |
| struct pixter_setting *settings; |
| struct pixter_dbgfs_data *dbgfs_data; |
| u32 reg_val, i, j; |
| int ret; |
| |
| /* allocate sensor device & init sub device */ |
| dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| mutex_init(&dev->input_lock); |
| |
| dev->dbg_timing.mipi_clk = PIXTER_DEF_CLOCK; |
| |
| v4l2_i2c_subdev_init(&dev->sd, client, &pixter_ops); |
| |
| if (client->dev.platform_data) { |
| dev->platform_data = client->dev.platform_data; |
| ret = dev->platform_data->csi_cfg(&dev->sd, 1); |
| if (ret) |
| goto out_free; |
| if (dev->platform_data->get_camera_caps) |
| caps = dev->platform_data->get_camera_caps(); |
| else |
| caps = atomisp_get_default_camera_caps(); |
| dev->caps = caps; |
| } |
| |
| dev->mipi_info = v4l2_get_subdev_hostdata(&dev->sd); |
| if (!dev->mipi_info) { |
| dev_err(&client->dev, "Faild to get mipi info.\n"); |
| goto out_free; |
| } |
| |
| /* Get the number of mipi lanes */ |
| dev->dbg_timing.mipi_lanes_num = dev->mipi_info->num_lanes; |
| |
| dev->regmap = devm_regmap_init_i2c(client, |
| &pixter_reg_config); |
| if (IS_ERR(dev->regmap)) { |
| ret = PTR_ERR(dev->regmap); |
| dev_err(&client->dev, |
| "Failed to allocate register map: %d\n", ret); |
| goto out_free; |
| } |
| |
| /* Load Pixter settings */ |
| pixter_write_reg(&dev->sd, PIXTER_SDRAM_BASE, 0); |
| pixter_read_reg(&dev->sd, PIXTER_MAGIC_ADDR, ®_val); |
| if (reg_val != PIXTER_MAGIC) { |
| dev_err(&client->dev, |
| "PIXTER magic does not match. Got 0x%X\n", reg_val); |
| ret = -EIO; |
| goto out_free; |
| } |
| pixter_read_reg(&dev->sd, PIXTER_SETTING_NUM, &dev->setting_num); |
| dev->settings = devm_kzalloc(&client->dev, |
| sizeof(struct pixter_setting) * |
| dev->setting_num, GFP_KERNEL); |
| if (!dev->settings) { |
| dev_err(&client->dev, "OOM when allocating settings.\n"); |
| ret = -ENOMEM; |
| goto out_free; |
| } |
| settings = dev->settings; |
| |
| ret = pixter_read_buf(&dev->sd, PIXTER_SETTING_START, |
| sizeof(struct pixter_setting) * dev->setting_num, |
| settings); |
| if (ret) { |
| dev_err(&client->dev, "Failed to read Pixter settings\n"); |
| goto out_free; |
| } |
| |
| /* Find settings that match the current device. */ |
| for (i = 0, j = 0; i < dev->setting_num; i++) { |
| if (caps->sensor[0].stream_num == settings[i].valid_vc_num) |
| settings[j++] = settings[i]; |
| } |
| dev->setting_num = j; |
| dev_info(&client->dev, "Setting num=%d\n", dev->setting_num); |
| if (!dev->setting_num) { |
| dev_err(&client->dev, "No matched settings loaded.\n"); |
| ret = -ENODEV; |
| 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->format.code = format_bridge[ |
| settings[0].vc[settings[0].def_vc].format].v4l2_format; |
| |
| /* |
| * sd->name is updated with sensor driver name by the v4l2. |
| * change it to sensor name in this case. |
| */ |
| if (dev->mipi_info->port == ATOMISP_CAMERA_PORT_PRIMARY) |
| pixter_name = PIXTER_0; |
| else if(dev->mipi_info->port == ATOMISP_CAMERA_PORT_SECONDARY) |
| pixter_name = PIXTER_1; |
| else |
| pixter_name = PIXTER_2; |
| snprintf(dev->sd.name, sizeof(dev->sd.name), "%s %d-%04x", |
| pixter_name, i2c_adapter_id(client->adapter), client->addr); |
| |
| dev_info(&client->dev, "%s dev->sd.name: %s\n", __func__, dev->sd.name); |
| |
| dev->sd.entity.ops = &pixter_entity_ops; |
| dev->sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; |
| |
| ret = v4l2_ctrl_handler_init(&dev->ctrl_handler, ARRAY_SIZE(ctrls)); |
| if (ret) |
| goto out_free; |
| |
| for (i = 0; i < ARRAY_SIZE(ctrls); i++) |
| v4l2_ctrl_new_custom(&dev->ctrl_handler, &ctrls[i], NULL); |
| |
| if (dev->ctrl_handler.error) { |
| ret = -EINVAL; |
| goto out_free; |
| } |
| |
| /* 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); |
| |
| ret = media_entity_init(&dev->sd.entity, 1, &dev->pad, 0); |
| if (ret) |
| goto out_free; |
| |
| /* Create debugfs nodes. */ |
| dev->dbgfs_data = devm_kzalloc(&client->dev, |
| sizeof(struct pixter_dbgfs_data) * |
| (ARRAY_SIZE(dbgfs) + dev->setting_num + 1), GFP_KERNEL); |
| if (!dev->dbgfs_data) |
| goto out_free; |
| dbgfs_data = dev->dbgfs_data; |
| dbgfs_data[0].entry = debugfs_create_dir(pixter_name, NULL); |
| for (i = 1; i < ARRAY_SIZE(dbgfs); i++) { |
| struct dentry *parent; |
| for (j = 0; j < i; j++) { |
| if (!strcmp(dbgfs[i].parent, dbgfs[j].name)) |
| break; |
| } |
| if (j == i) |
| continue; |
| parent = dbgfs_data[j].entry; |
| dbgfs_data[i].dev = dev; |
| dbgfs_data[i].ptr = (u8*)dev + dbgfs[i].offset; |
| if (dbgfs[i].type == DBGFS_DIR) |
| dbgfs_data[i].entry = debugfs_create_dir(dbgfs[i].name, |
| parent); |
| else |
| dbgfs_data[i].entry = debugfs_create_file(dbgfs[i].name, |
| dbgfs[i].mode, parent, |
| &dbgfs_data[i], &pixter_dbgfs_fops); |
| } |
| /* Create setting nodes. */ |
| dev->setting_en = devm_kzalloc(&client->dev, |
| sizeof(u32) * dev->setting_num, GFP_KERNEL); |
| if (!dev->setting_en) |
| goto out_free; |
| dbgfs_data[i].entry = debugfs_create_dir("settings", |
| dbgfs_data[0].entry); |
| for (j = 0; j < dev->setting_num; j++) { |
| char setting_name[32]; |
| u32 idx = i + j + 1; |
| struct pixter_vc_setting *vc = |
| &settings[j].vc[settings[j].def_vc]; |
| |
| dev->setting_en[j] = 1; |
| snprintf(setting_name, 32, "%d.%dx%d_%s@%d", j, vc->width, |
| vc->height, format_bridge[vc->format].name, vc->fps); |
| dbgfs_data[idx].dev = dev; |
| dbgfs_data[idx].ptr = &dev->setting_en[j]; |
| dbgfs_data[idx].entry = debugfs_create_file(setting_name, |
| S_IRUSR|S_IWUSR, dbgfs_data[i].entry, |
| &dbgfs_data[idx], &pixter_dbgfs_fops); |
| } |
| pixter_read_mipi_timing(&dev->sd); |
| |
| return 0; |
| |
| out_free: |
| pixter_remove(client); |
| return ret; |
| } |
| |
| static const struct i2c_device_id pixter_ids[] = { |
| {PIXTER_0, 0}, |
| {PIXTER_1, 0}, |
| {PIXTER_2, 0}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, pixter_ids); |
| |
| static struct i2c_driver pixter_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = PIXTER_DRV, |
| }, |
| .probe = pixter_probe, |
| .remove = pixter_remove, |
| .id_table = pixter_ids, |
| }; |
| |
| module_i2c_driver(pixter_driver); |
| |
| MODULE_DESCRIPTION("Pixter MIPI CSI simulator driver."); |
| MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); |
| MODULE_LICENSE("GPL"); |