1259 lines
31 KiB
C
1259 lines
31 KiB
C
/*
|
|
* mt9m021.c - MT9M021 driver
|
|
*
|
|
* Copyright (c) 2019, RidgeRun. All rights reserved.
|
|
*
|
|
* Author: Enrique Ramírez (enrique.ramirez@ridgerun.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define DEBUG 1
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/module.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <media/tegra_v4l2_camera.h>
|
|
#include <media/tegracam_core.h>
|
|
|
|
#include "camera_gpio.h"
|
|
#include "mt9m021_mode_tbls.h"
|
|
|
|
/***************************************************
|
|
MT9M021 Image Sensor Registers
|
|
****************************************************/
|
|
|
|
#define MT9M021_CHIP_ID_REG 0x3000
|
|
#define MT9M021_RESET_REG 0x301A
|
|
#define MT9M021_SEQ_CTRL_PORT 0x3088
|
|
#define MT9M021_SEQ_DATA_PORT 0x3086
|
|
#define MT9M021_ANALOG_REG 0x3ED6
|
|
#define MT9M021_TEST_RAW_MODE 0x307A
|
|
#define MT9M021_DARK_CTRL 0x3044
|
|
#define MT9M021_DATA_PEDESTAL 0x301E
|
|
#define MT9M021_COLUMN_CORRECTION 0x30D4
|
|
#define MT9M021_FLASH 0x3046
|
|
|
|
#define MT9M021_VT_SYS_CLK_DIV 0x302A
|
|
#define MT9M021_VT_PIX_CLK_DIV 0x302C
|
|
#define MT9M021_PRE_PLL_CLK_DIV 0x302E
|
|
#define MT9M021_PLL_MULTIPLIER 0x3030
|
|
#define MT9M021_DIGITAL_TEST 0x30B0
|
|
|
|
#define MT9M021_Y_ADDR_START 0x3002
|
|
#define MT9M021_X_ADDR_START 0x3004
|
|
#define MT9M021_Y_ADDR_END 0x3006
|
|
#define MT9M021_X_ADDR_END 0x3008
|
|
#define MT9M021_FRAME_LENGTH_LINES 0x300A
|
|
#define MT9M021_LINE_LENGTH_PCK 0x300C
|
|
#define MT9M021_COARSE_INT_TIME 0x3012
|
|
#define MT9M021_FINE_INT_TIME 0x3014
|
|
#define MT9M021_COARSE_INT_TIME_CB 0x3016
|
|
#define MT9M021_FINE_INT_TIME_CB 0x3018
|
|
#define MT9M021_FRAME_LENGTH_LINES_CB 0x30AA
|
|
#define MT9M021_X_ODD_INC 0x30A2
|
|
#define MT9M021_Y_ODD_INC 0x30A6
|
|
#define MT9M021_READ_MODE 0x3040
|
|
#define MT9M021_TEST_PATTERN 0x3070
|
|
#define MT9M021_DIGITAL_BINNING 0x3032
|
|
|
|
#define MT9M021_AE_CTRL_REG 0x3100
|
|
#define MT9M021_AE_LUMA_TARGET_REG 0x3102
|
|
#define MT9M021_EMBEDDED_DATA_CTRL 0x3064
|
|
#define MT9M021_DATAPATH_SELECT 0X306E
|
|
|
|
#define MT9M021_GREEN1_GAIN 0x3056
|
|
#define MT9M021_BLUE_GAIN 0x3058
|
|
#define MT9M021_RED_GAIN 0x305A
|
|
#define MT9M021_GREEN2_GAIN 0x305C
|
|
#define MT9M021_GLOBAL_GAIN 0x305E
|
|
#define MT9M021_GREEN1_GAIN_CB 0x30BC
|
|
#define MT9M021_BLUE_GAIN_CB 0x30BE
|
|
#define MT9M021_RED_GAIN_CB 0x30C0
|
|
#define MT9M021_GREEN2_GAIN_CB 0x30C2
|
|
#define MT9M021_GLOBAL_GAIN_CB 0x30C4
|
|
|
|
/***************************************************
|
|
MT9M021 Image Sensor Defines
|
|
****************************************************/
|
|
|
|
#define BRIDGE_I2C_ADDR 0x0e
|
|
#define MT9M021_I2C_ADDR 0x10
|
|
#define MT9M021_CHIP_ID 0x2401
|
|
|
|
#define MT9M021_PIXEL_ARRAY_WIDTH 1280
|
|
#define MT9M021_PIXEL_ARRAY_HEIGHT 960
|
|
|
|
#define MT9M021_EXT_FREQ 24000000
|
|
#define MT9M021_TARGET_FREQ 74250000
|
|
#define MT9M021_PLL_M 99
|
|
#define MT9M021_PLL_N 4
|
|
#define MT9M021_PLL_P1 1
|
|
#define MT9M021_PLL_P2 8
|
|
|
|
#define MT9M021_ROW_START_MIN 0
|
|
#define MT9M021_ROW_START_MAX 960
|
|
#define MT9M021_ROW_START_DEF 0x0078
|
|
#define MT9M021_COLUMN_START_MIN 0
|
|
#define MT9M021_COLUMN_START_MAX 1280
|
|
#define MT9M021_COLUMN_START_DEF 0
|
|
#define MT9M021_WINDOW_HEIGHT_MIN 2
|
|
#define MT9M021_WINDOW_HEIGHT_MAX 720
|
|
#define MT9M021_WINDOW_HEIGHT_DEF 720
|
|
#define MT9M021_WINDOW_WIDTH_MIN 2
|
|
#define MT9M021_WINDOW_WIDTH_MAX 1280
|
|
#define MT9M021_WINDOW_WIDTH_DEF 1280
|
|
#define MT9M021_HOR_AND_VER_BIN 0x0020
|
|
#define MT9M021_HOR_BIN 0x0011
|
|
#define MT9M021_DISABLE_BINNING 0x0000
|
|
|
|
#define MT9M021_RESET 0x00D9
|
|
#define MT9M021_STREAM_OFF 0x00D8
|
|
#define MT9M021_STREAM_ON 0x00DC
|
|
#define MT9M021_MASTER_MODE 0x10DC
|
|
#define MT9M021_TRIGGER_MODE 0x19D8
|
|
|
|
#define MT9M021_ANALOG_GAIN_MIN 0x0
|
|
#define MT9M021_ANALOG_GAIN_MAX 0x3
|
|
#define MT9M021_ANALOG_GAIN_DEF 0x0
|
|
#define MT9M021_ANALOG_GAIN_SHIFT 4
|
|
#define MT9M021_ANALOG_GAIN_MASK 0x0030
|
|
|
|
#define MT9M021_GLOBAL_GAIN_MIN 100
|
|
#define MT9M021_GLOBAL_GAIN_MAX 762
|
|
#define MT9M021_GLOBAL_GAIN_DEF 100
|
|
|
|
#define MT9M021_COARSE_INT_TIME_MIN 0x0001
|
|
#define MT9M021_COARSE_INT_TIME_MAX 0x02A0
|
|
#define MT9M021_COARSE_INT_TIME_DEF 0x01C2
|
|
|
|
#define MT9M021_LLP_RECOMMENDED 0x0672
|
|
|
|
#define MT9M021_TEST_PATTERN_VAL 0x0
|
|
|
|
#define MT9M021_GAIN_1X 0x0
|
|
#define MT9M021_GAIN_2X 0x1
|
|
#define MT9M021_GAIN_4X 0x2
|
|
#define MT9M021_GAIN_8X 0x3
|
|
|
|
#define MT9M021_GAIN_1X_FIXED 100
|
|
#define MT9M021_GAIN_2X_FIXED 200
|
|
#define MT9M021_GAIN_4X_FIXED 400
|
|
#define MT9M021_GAIN_8X_FIXED 800
|
|
|
|
#define MT9M021_HFLIP_MASK 0x4000
|
|
#define MT9M021_VFLIP_MASK 0x8000
|
|
|
|
/***************************************************
|
|
TC358746AXBG MIPI Converter Defines
|
|
****************************************************/
|
|
|
|
struct mipi_bridge_settings {
|
|
uint8_t len;
|
|
uint16_t addr;
|
|
uint32_t data;
|
|
};
|
|
|
|
struct mipi_bridge_settings bridge_config[] = {
|
|
{2, 0x0004, 0x0004}, // Parallel Data Format mode0 + PCLK Inverted
|
|
{2, 0x0002, 0x0001}, // reset 1
|
|
{2, 0x0002, 0x0000}, // reset 0
|
|
{2, 0x0016, 0x50F9}, // set the input and feedback frequency division ratio
|
|
{2, 0x0018, 0x0213}, // 50% maximum loop bandwidth + PLL clock enable + normal operation + PLL enable
|
|
{2, 0x0006, 0x0030}, // FIFO level 3
|
|
|
|
#ifdef USE_RAW8
|
|
{2, 0x0008, 0x0000}, // data format RAW8
|
|
#else
|
|
{2, 0x0008, 0x0020}, // data format RAW12
|
|
#endif
|
|
|
|
{2, 0x0022, 0x0780}, // word count (bytes per line) 2560
|
|
// {4, 0x0140,0x00000000},
|
|
// {4, 0x0144,0x00000000},
|
|
// {4, 0x0148,0x00000001},
|
|
// {4, 0x014C,0x00000001},
|
|
// {4, 0x0150,0x00000001},
|
|
{4, 0x0210, 0x00002C00},
|
|
{4, 0x0214, 0x00000005},
|
|
{4, 0x0218, 0x00001E06},
|
|
{4, 0x021C, 0x00000004},
|
|
{4, 0x0220, 0x00000406},
|
|
{4, 0x0224, 0x00004988},
|
|
{4, 0x0228, 0x0000000C},
|
|
{4, 0x022C, 0x00000006},
|
|
{4, 0x0234, 0x0000001F}, // Voltage regulator enable for data 0-3 and clock lanes.
|
|
// {4, 0x0238, 0x00000001}, // Continuous clock mode. Maintains the clock lane output regardless of data lane operation
|
|
{4, 0x0238, 0x00000000}, // Discontinuous clock mode.
|
|
{4, 0x0518, 0x00000001}, // CSI start
|
|
|
|
{4, 0x0500, 0xA30080A1}, // 1 data lane
|
|
// {4, 0x0500, 0xA30080A3}, // 2 data lanes
|
|
// {4, 0x0500, 0xA30080A7}, // 4 data lanes
|
|
|
|
{4, 0x0204, 0x00000001}, // TX PPI starts
|
|
{2, 0x0004, 0x0044},
|
|
};
|
|
|
|
static int mt9m021_bridge_setup(struct i2c_client *client)
|
|
{
|
|
struct i2c_msg msg[2];
|
|
uint8_t buf[6];
|
|
struct mipi_bridge_settings settings;
|
|
uint16_t __addr;
|
|
uint32_t __data;
|
|
int ret;
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bridge_config); i++) {
|
|
settings = bridge_config[i];
|
|
|
|
__addr = cpu_to_be16(settings.addr);
|
|
__data = cpu_to_be32(settings.data);
|
|
|
|
buf[0] = (uint8_t) (__addr);
|
|
buf[1] = (uint8_t) (__addr >> 8);
|
|
|
|
buf[2] = (uint8_t) (__data >> 16);
|
|
buf[3] = (uint8_t) (__data >> 24);
|
|
|
|
buf[4] = (uint8_t) (__data);
|
|
buf[5] = (uint8_t) (__data >> 8);
|
|
|
|
msg[0].addr = BRIDGE_I2C_ADDR;
|
|
msg[0].flags = 0;
|
|
msg[0].len = settings.len + 2;
|
|
msg[0].buf = buf;
|
|
|
|
ret = i2c_transfer(client->adapter, msg, 1);
|
|
|
|
if (settings.len == 2)
|
|
dev_dbg(&client->dev, "BRIDGE %s: 0x%04x to 0x%04x\n",
|
|
__func__, settings.data, settings.addr);
|
|
else
|
|
dev_dbg(&client->dev, "BRIDGE %s: 0x%08x to 0x%04x\n",
|
|
__func__, settings.data, settings.addr);
|
|
|
|
if (ret < 0) {
|
|
dev_err(&client->dev,
|
|
"BRIDGE %s failed at 0x%04x error %d\n",
|
|
__func__, settings.addr, ret);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id mt9m021_of_match[] = {
|
|
{.compatible = "nvidia,mt9m021",},
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, mt9m021_of_match);
|
|
|
|
static const u32 ctrl_cid_list[] = {
|
|
TEGRA_CAMERA_CID_GAIN,
|
|
TEGRA_CAMERA_CID_EXPOSURE,
|
|
TEGRA_CAMERA_CID_FRAME_RATE,
|
|
TEGRA_CAMERA_CID_SENSOR_MODE_ID,
|
|
V4L2_CID_ANALOGUE_GAIN,
|
|
V4L2_CID_GAIN_RED,
|
|
V4L2_CID_GAIN_GREENR,
|
|
V4L2_CID_GAIN_GREENB,
|
|
V4L2_CID_GAIN_BLUE,
|
|
V4L2_CID_TEST_PATTERN,
|
|
V4L2_CID_FLASH_LED_MODE,
|
|
V4L2_CID_HFLIP,
|
|
V4L2_CID_VFLIP,
|
|
};
|
|
|
|
struct mt9m021 {
|
|
struct i2c_client *i2c_client;
|
|
struct v4l2_subdev *subdev;
|
|
const char *trigger_mode;
|
|
bool flash_en;
|
|
struct camera_common_data *s_data;
|
|
struct tegracam_device *tc_dev;
|
|
};
|
|
|
|
static const struct regmap_config sensor_regmap_config = {
|
|
.reg_bits = 16,
|
|
.val_bits = 8,
|
|
};
|
|
|
|
static inline int mt9m021_read_reg(struct camera_common_data *s_data,
|
|
u16 addr, u8 * val)
|
|
{
|
|
int err = 0;
|
|
u32 reg_val = 0;
|
|
|
|
err = regmap_read(s_data->regmap, addr, ®_val);
|
|
*val = reg_val & 0xff;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_read_reg16(struct camera_common_data *s_data,
|
|
u16 addr, u16 * val)
|
|
{
|
|
u8 val_hi;
|
|
u8 val_lo;
|
|
int err = 0;
|
|
|
|
if (!s_data)
|
|
return -EFAULT;
|
|
|
|
err = mt9m021_read_reg(s_data, addr, &val_hi);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mt9m021_read_reg(s_data, addr + 1, &val_lo);
|
|
*val = (val_hi << 8) | val_lo;
|
|
|
|
return err;
|
|
}
|
|
|
|
static inline int mt9m021_write_reg(struct camera_common_data *s_data,
|
|
u16 addr, u8 val)
|
|
{
|
|
int err = 0;
|
|
|
|
err = regmap_write(s_data->regmap, addr, val);
|
|
if (err)
|
|
dev_err(s_data->dev, "%s: i2c write failed, 0x%x = %x",
|
|
__func__, addr, val);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mt9m021_write_reg16(struct camera_common_data *s_data, u16 addr, u16 val)
|
|
{
|
|
int err;
|
|
u8 data[2];
|
|
|
|
dev_dbg(s_data->dev, "%s: 0x%04x to 0x%04x\n", __func__, val, addr);
|
|
|
|
data[0] = (val >> 8) & 0xff;;
|
|
data[1] = (val & 0xff);
|
|
|
|
err = regmap_raw_write(s_data->regmap, addr, data, 2);
|
|
if (err)
|
|
dev_err(s_data->dev, "%s: i2c write failed, 0x%x = %x",
|
|
__func__, addr, val);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mt9m021_update_bits(struct camera_common_data *s_data, u16 addr, u16 val,
|
|
u16 mask)
|
|
{
|
|
u16 reg16;
|
|
int err;
|
|
|
|
err = mt9m021_read_reg16(s_data, mask, ®16);
|
|
if (err)
|
|
return err;
|
|
|
|
reg16 = (reg16 & ~mask) | (val & mask);
|
|
err = mt9m021_write_reg16(s_data, addr, reg16);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_write_table(struct mt9m021 *priv, const mt9m021_reg table[])
|
|
{
|
|
return regmap_util_write_table_16_as_8(priv->s_data->regmap, table,
|
|
NULL, 0, MT9M021_TABLE_WAIT_MS,
|
|
MT9M021_TABLE_END);
|
|
}
|
|
|
|
static int mt9m021_set_group_hold(struct tegracam_device *tc_dev, bool val)
|
|
{
|
|
/* mt9m021 does not support group hold */
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_set_gain(struct tegracam_device *tc_dev, s64 val)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct device *dev = tc_dev->dev;
|
|
int err = 0;
|
|
u16 gain_mul;
|
|
u16 reg16 = 0;
|
|
u64 gain = 0;
|
|
|
|
dev_dbg(dev, "Setting Gain to: %lld", val);
|
|
|
|
if (val >= MT9M021_GAIN_8X_FIXED) {
|
|
gain_mul = MT9M021_GAIN_8X;
|
|
} else if (val >= MT9M021_GAIN_4X_FIXED) {
|
|
gain_mul = MT9M021_GAIN_4X;
|
|
} else if (val >= MT9M021_GAIN_2X_FIXED) {
|
|
gain_mul = MT9M021_GAIN_2X;
|
|
} else {
|
|
gain_mul = MT9M021_GAIN_1X;
|
|
}
|
|
|
|
/*
|
|
* Digital gain equation:
|
|
*
|
|
* RANGE: 1x, 7.97x
|
|
* STEPS: 1/32
|
|
*
|
|
* SCALE FACTOR = 3
|
|
*
|
|
* min_gain_val = 102
|
|
* max_gain_val = 160
|
|
* gain_factor = 3
|
|
*
|
|
* gain accepts mapping to range 32 - 53
|
|
*/
|
|
gain = val / 3;
|
|
|
|
/* Update analog gain multiplier */
|
|
err = mt9m021_read_reg16(s_data, MT9M021_DIGITAL_TEST, ®16);
|
|
if (err)
|
|
goto exit;
|
|
reg16 =
|
|
(reg16 & ~MT9M021_ANALOG_GAIN_MASK) |
|
|
((gain_mul << MT9M021_ANALOG_GAIN_SHIFT) &
|
|
MT9M021_ANALOG_GAIN_MASK);
|
|
err = mt9m021_write_reg16(s_data, MT9M021_DIGITAL_TEST, reg16);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* Update global gain */
|
|
err =
|
|
mt9m021_write_reg16(s_data, MT9M021_GLOBAL_GAIN, gain);
|
|
if (err)
|
|
goto exit;
|
|
err =
|
|
mt9m021_write_reg16(s_data, MT9M021_GLOBAL_GAIN_CB, gain);
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
dev_err(dev, "Gain control error: %d", err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_frame_length(struct mt9m021 *priv, s64 val)
|
|
{
|
|
struct device *dev = &priv->i2c_client->dev;
|
|
struct camera_common_data *s_data = priv->s_data;
|
|
int err = 0;
|
|
|
|
dev_dbg(dev, "Setting Frame Length to: %lld", val);
|
|
|
|
err = mt9m021_write_reg16(s_data, MT9M021_FRAME_LENGTH_LINES, val);
|
|
if (err)
|
|
return err;
|
|
msleep(30);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_set_frame_rate(struct tegracam_device *tc_dev, s64 val)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct mt9m021 *priv = (struct mt9m021 *)tc_dev->priv;
|
|
struct device *dev = tc_dev->dev;
|
|
const struct sensor_mode_properties *mode =
|
|
&s_data->sensor_props.sensor_modes[s_data->mode];
|
|
u64 frame_length;
|
|
int err;
|
|
|
|
dev_dbg(dev, "Setting Frame rate to: %lld", val);
|
|
|
|
/* Calculate frame-length */
|
|
frame_length = mode->signal_properties.pixel_clock.val *
|
|
mode->control_properties.framerate_factor /
|
|
mode->image_properties.line_length / val;
|
|
|
|
err = mt9m021_set_frame_length(priv, frame_length);
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
dev_err(dev, "Frame rate control error: %d", err);
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_coarse_time(struct mt9m021 *priv, s64 val)
|
|
{
|
|
struct device *dev = &priv->i2c_client->dev;
|
|
struct camera_common_data *s_data = priv->s_data;
|
|
int err = 0;
|
|
|
|
dev_dbg(dev, "Setting Coarse Time to: %lld", val);
|
|
|
|
err = mt9m021_write_reg16(s_data, MT9M021_COARSE_INT_TIME, val);
|
|
if (err)
|
|
return err;
|
|
err = mt9m021_write_reg16(s_data, MT9M021_COARSE_INT_TIME_CB, val);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_set_exposure(struct tegracam_device *tc_dev, s64 val)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct mt9m021 *priv = (struct mt9m021 *)tc_dev->priv;
|
|
struct device *dev = tc_dev->dev;
|
|
const struct sensor_mode_properties *mode =
|
|
&s_data->sensor_props.sensor_modes[s_data->mode];
|
|
u64 coarse_time;
|
|
int err = 0;
|
|
|
|
dev_dbg(dev, "Setting Exposure Time to : %lld", val);
|
|
|
|
coarse_time =
|
|
val * mode->signal_properties.pixel_clock.val /
|
|
mode->image_properties.line_length /
|
|
mode->control_properties.exposure_factor;
|
|
|
|
err = mt9m021_set_coarse_time(priv, coarse_time);
|
|
if (err)
|
|
goto exit;
|
|
|
|
return err;
|
|
|
|
exit:
|
|
dev_err(dev, "Exposure control error: %d", err);
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_analog_gain(struct tegracam_device *tc_dev, s64 val)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct device *dev = tc_dev->dev;
|
|
int err = 0;
|
|
u16 reg16 = 0;
|
|
|
|
dev_dbg(dev, "Setting Analog Gain to: %lld", val);
|
|
|
|
/* Update analog gain multiplier */
|
|
err = mt9m021_read_reg16(s_data, MT9M021_DIGITAL_TEST, ®16);
|
|
if (err)
|
|
goto exit;
|
|
reg16 =
|
|
(reg16 & ~MT9M021_ANALOG_GAIN_MASK) |
|
|
((val << MT9M021_ANALOG_GAIN_SHIFT) &
|
|
MT9M021_ANALOG_GAIN_MASK);
|
|
err = mt9m021_write_reg16(s_data, MT9M021_DIGITAL_TEST, reg16);
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
dev_err(dev, "Analog Gain control error: %d", err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_digital_gain(struct tegracam_device *tc_dev, s64 val,
|
|
int id)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct device *dev = tc_dev->dev;
|
|
u32 gain_reg;
|
|
u32 gain_cb_reg;
|
|
const char *gain_name;
|
|
int err = 0;
|
|
|
|
switch (id) {
|
|
case V4L2_CID_GAIN_RED:
|
|
gain_name = "Red Gain";
|
|
gain_reg = MT9M021_RED_GAIN;
|
|
gain_cb_reg = MT9M021_RED_GAIN_CB;
|
|
break;
|
|
case V4L2_CID_GAIN_GREENR:
|
|
gain_name = "GreenR Gain";
|
|
gain_reg = MT9M021_GREEN1_GAIN;
|
|
gain_cb_reg = MT9M021_GREEN1_GAIN_CB;
|
|
break;
|
|
case V4L2_CID_GAIN_GREENB:
|
|
gain_name = "GreenB Gain";
|
|
gain_reg = MT9M021_GREEN2_GAIN;
|
|
gain_cb_reg = MT9M021_GREEN2_GAIN_CB;
|
|
break;
|
|
case V4L2_CID_GAIN_BLUE:
|
|
gain_name = "Blue Gain";
|
|
gain_reg = MT9M021_BLUE_GAIN;
|
|
gain_cb_reg = MT9M021_BLUE_GAIN_CB;
|
|
break;
|
|
default:
|
|
dev_err(dev, "Unknown ctrl id %d", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(dev, "Setting %s to: %lld", gain_name, val);
|
|
|
|
err = mt9m021_write_reg16(s_data, gain_reg, val);
|
|
if (err)
|
|
goto exit;
|
|
err = mt9m021_write_reg16(s_data, gain_cb_reg, val);
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
dev_err(dev, "Digital Gain control error: %d", err);
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_test_pattern(struct tegracam_device *tc_dev, s32 val)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct device *dev = tc_dev->dev;
|
|
int err = 0;
|
|
|
|
dev_dbg(dev, "Setting Test Pattern to: %d", val);
|
|
|
|
if (!val)
|
|
err = mt9m021_write_reg16(s_data, MT9M021_TEST_PATTERN,
|
|
MT9M021_TEST_PATTERN_VAL);
|
|
else
|
|
err = mt9m021_write_reg16(s_data, MT9M021_TEST_PATTERN,
|
|
val);
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
|
|
exit:
|
|
dev_err(dev, "Test Pattern control error: %d", err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_flash(struct tegracam_device *tc_dev, s32 val)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct mt9m021 *priv = (struct mt9m021 *)tc_dev->priv;
|
|
struct device *dev = tc_dev->dev;
|
|
int err = 0;
|
|
|
|
dev_dbg(dev, "Setting Flash to: %d", val);
|
|
|
|
switch(val) {
|
|
case V4L2_FLASH_LED_MODE_NONE:
|
|
err = mt9m021_write_reg16(s_data, MT9M021_FLASH, 0x0000);
|
|
priv->flash_en = false;
|
|
break;
|
|
case V4L2_FLASH_LED_MODE_FLASH:
|
|
err = mt9m021_write_reg16(s_data, MT9M021_FLASH, 0x0100);
|
|
priv->flash_en = true;
|
|
break;
|
|
default:
|
|
dev_err(dev, "Unsupported flash mode requested: %d\n", val);
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
exit:
|
|
dev_err(dev, "Flash control error: %d", err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_set_flip(struct tegracam_device *tc_dev, s32 val, int id)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct device *dev = tc_dev->dev;
|
|
int err = 0;
|
|
|
|
dev_dbg(dev, "Setting Flip to: %d", val);
|
|
|
|
switch(id) {
|
|
case V4L2_CID_HFLIP:
|
|
err = mt9m021_update_bits(s_data, MT9M021_READ_MODE,
|
|
val << 14, MT9M021_HFLIP_MASK);
|
|
break;
|
|
case V4L2_CID_VFLIP:
|
|
err = mt9m021_update_bits(s_data, MT9M021_READ_MODE,
|
|
val << 15, MT9M021_VFLIP_MASK);
|
|
break;
|
|
default:
|
|
dev_err(dev, "Unsupported flip mode requested: %d\n", val);
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
goto exit;
|
|
|
|
return 0;
|
|
exit:
|
|
dev_err(dev, "Flip control error: %d", err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct tegracam_ctrl_ops mt9m021_ctrl_ops = {
|
|
.numctrls = ARRAY_SIZE(ctrl_cid_list),
|
|
.ctrl_cid_list = ctrl_cid_list,
|
|
.set_gain = mt9m021_set_gain,
|
|
.set_exposure = mt9m021_set_exposure,
|
|
.set_frame_rate = mt9m021_set_frame_rate,
|
|
.set_group_hold = mt9m021_set_group_hold,
|
|
.set_analog_gain = mt9m021_set_analog_gain,
|
|
.set_digital_gain = mt9m021_set_digital_gain,
|
|
.set_test_pattern = mt9m021_set_test_pattern,
|
|
.set_flash = mt9m021_set_flash,
|
|
.set_flip = mt9m021_set_flip,
|
|
};
|
|
|
|
static int mt9m021_power_on(struct camera_common_data *s_data)
|
|
{
|
|
int err = 0;
|
|
struct camera_common_power_rail *pw = s_data->power;
|
|
struct camera_common_pdata *pdata = s_data->pdata;
|
|
struct device *dev = s_data->dev;
|
|
|
|
dev_dbg(dev, "%s: power on\n", __func__);
|
|
if (pdata && pdata->power_on) {
|
|
err = pdata->power_on(pw);
|
|
if (err)
|
|
dev_err(dev, "%s failed.\n", __func__);
|
|
else
|
|
pw->state = SWITCH_ON;
|
|
return err;
|
|
}
|
|
|
|
if (pw->reset_gpio) {
|
|
gpio_set_value(pw->reset_gpio, 1);
|
|
usleep_range(50000, 51000);
|
|
}
|
|
|
|
pw->state = SWITCH_ON;
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int mt9m021_power_off(struct camera_common_data *s_data)
|
|
{
|
|
int err = 0;
|
|
struct camera_common_power_rail *pw = s_data->power;
|
|
struct camera_common_pdata *pdata = s_data->pdata;
|
|
struct device *dev = s_data->dev;
|
|
|
|
dev_dbg(dev, "%s: power off\n", __func__);
|
|
|
|
if (pdata && pdata->power_off) {
|
|
err = pdata->power_off(pw);
|
|
if (!err)
|
|
goto power_off_done;
|
|
else
|
|
dev_err(dev, "%s failed.\n", __func__);
|
|
return err;
|
|
}
|
|
usleep_range(2000, 2010);
|
|
|
|
if (pw->reset_gpio) {
|
|
gpio_set_value(pw->reset_gpio, 0);
|
|
usleep_range(2000, 2010);
|
|
}
|
|
|
|
power_off_done:
|
|
pw->state = SWITCH_OFF;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_power_put(struct tegracam_device *tc_dev)
|
|
{
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct camera_common_power_rail *pw = s_data->power;
|
|
|
|
if (unlikely(!pw))
|
|
return -EFAULT;
|
|
|
|
if (likely(pw->dvdd))
|
|
regulator_disable(pw->dvdd);
|
|
|
|
if (likely(pw->avdd))
|
|
regulator_put(pw->avdd);
|
|
|
|
if (likely(pw->iovdd))
|
|
regulator_put(pw->iovdd);
|
|
|
|
pw->dvdd = NULL;
|
|
pw->avdd = NULL;
|
|
pw->iovdd = NULL;
|
|
|
|
if (likely(pw->reset_gpio))
|
|
gpio_free(pw->reset_gpio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_power_get(struct tegracam_device *tc_dev)
|
|
{
|
|
struct device *dev = tc_dev->dev;
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct camera_common_power_rail *pw = s_data->power;
|
|
struct camera_common_pdata *pdata = s_data->pdata;
|
|
const char *mclk_name;
|
|
struct clk *parent;
|
|
int err = 0;
|
|
int ret = 0;
|
|
|
|
mclk_name = pdata->mclk_name ? pdata->mclk_name : "extperiph1";
|
|
pw->mclk = devm_clk_get(dev, mclk_name);
|
|
if (IS_ERR(pw->mclk)) {
|
|
dev_err(dev, "unable to get clock %s\n", mclk_name);
|
|
return PTR_ERR(pw->mclk);
|
|
}
|
|
|
|
parent = devm_clk_get(dev, "pllp_grtba");
|
|
if (IS_ERR(parent))
|
|
dev_err(dev, "devm_clk_get failed for pllp_grtba");
|
|
else
|
|
clk_set_parent(pw->mclk, parent);
|
|
|
|
pw->reset_gpio = pdata->reset_gpio;
|
|
|
|
if (pdata->use_cam_gpio) {
|
|
err = cam_gpio_register(dev, pw->pwdn_gpio);
|
|
if (err)
|
|
dev_err(dev, "%s ERR can't register cam gpio %u!\n",
|
|
__func__, pw->pwdn_gpio);
|
|
} else {
|
|
if (gpio_is_valid(pw->reset_gpio)) {
|
|
ret = gpio_request(pw->reset_gpio, "cam_reset_gpio");
|
|
if (ret < 0) {
|
|
dev_dbg(dev, "%s can't request reset_gpio %d\n",
|
|
__func__, ret);
|
|
}
|
|
gpio_direction_output(pw->reset_gpio, 1);
|
|
}
|
|
}
|
|
|
|
pw->state = SWITCH_OFF;
|
|
return err;
|
|
}
|
|
|
|
static struct camera_common_pdata *mt9m021_parse_dt(struct tegracam_device
|
|
*tc_dev)
|
|
{
|
|
struct device *dev = tc_dev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct camera_common_pdata *board_priv_pdata;
|
|
const struct of_device_id *match;
|
|
struct camera_common_pdata *ret = NULL;
|
|
int err = 0;
|
|
int gpio;
|
|
|
|
if (!np)
|
|
return NULL;
|
|
|
|
match = of_match_device(mt9m021_of_match, dev);
|
|
if (!match) {
|
|
dev_err(dev, "Failed to find matching dt id\n");
|
|
return NULL;
|
|
}
|
|
|
|
board_priv_pdata = devm_kzalloc(dev,
|
|
sizeof(*board_priv_pdata), GFP_KERNEL);
|
|
if (!board_priv_pdata)
|
|
return NULL;
|
|
|
|
gpio = of_get_named_gpio(np, "reset-gpios", 0);
|
|
if (gpio < 0) {
|
|
if (gpio == -EPROBE_DEFER)
|
|
ret = ERR_PTR(-EPROBE_DEFER);
|
|
dev_err(dev, "reset-gpios not found\n");
|
|
goto error;
|
|
}
|
|
board_priv_pdata->reset_gpio = (unsigned int)gpio;
|
|
|
|
err = of_property_read_string(np, "mclk", &board_priv_pdata->mclk_name);
|
|
if (err)
|
|
dev_dbg(dev, "mclk name not present, "
|
|
"assume sensor driven externally\n");
|
|
|
|
board_priv_pdata->has_eeprom = of_property_read_bool(np, "has-eeprom");
|
|
|
|
return board_priv_pdata;
|
|
|
|
error:
|
|
devm_kfree(dev, board_priv_pdata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mt9m021_sequencer_settings(struct mt9m021 *priv)
|
|
{
|
|
struct device *dev = &priv->i2c_client->dev;
|
|
struct camera_common_data *s_data = priv->s_data;
|
|
int i, ret;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
ret = mt9m021_write_reg16(s_data, MT9M021_SEQ_CTRL_PORT, 0x8000);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt9m021_seq_data); i++) {
|
|
ret =
|
|
mt9m021_write_reg16(s_data, MT9M021_SEQ_DATA_PORT,
|
|
mt9m021_seq_data[i]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mt9m021_col_correction(struct mt9m021 *priv)
|
|
{
|
|
struct device *dev = &priv->i2c_client->dev;
|
|
struct camera_common_data *s_data = priv->s_data;
|
|
int ret;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
/* Disable Streaming */
|
|
ret = mt9m021_write_table(priv, mode_table[MT9M021_MODE_STOP_STREAM]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Disable column correction */
|
|
ret = mt9m021_write_reg16(s_data, MT9M021_COLUMN_CORRECTION, 0x0007);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Enable Streaming */
|
|
ret = mt9m021_write_table(priv, mode_table[MT9M021_MODE_START_STREAM]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Disable Streaming */
|
|
ret = mt9m021_write_table(priv, mode_table[MT9M021_MODE_STOP_STREAM]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Enable column correction */
|
|
ret = mt9m021_write_reg16(s_data, MT9M021_COLUMN_CORRECTION, 0xE007);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mt9m021_set_mode(struct tegracam_device *tc_dev)
|
|
{
|
|
struct mt9m021 *priv = (struct mt9m021 *)tegracam_get_privdata(tc_dev);
|
|
struct camera_common_data *s_data = tc_dev->s_data;
|
|
struct device *dev = tc_dev->dev;
|
|
int ret;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
ret = mt9m021_bridge_setup(priv->i2c_client);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: Failed to setup mipi bridge\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = mt9m021_sequencer_settings(priv);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: Failed to setup sequencer\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = mt9m021_write_table(priv, mode_table[MT9M021_MODE_PLL_SETUP]);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: Failed to set pll setup\n", __func__);
|
|
return ret;
|
|
}
|
|
msleep(100);
|
|
|
|
ret = mt9m021_col_correction(priv);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: Failed to setup column correction\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = mt9m021_write_table(priv, mode_table[s_data->mode]);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%s: Failed to setup sensor mode\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = mt9m021_set_flash(tc_dev, priv->flash_en);
|
|
return ret;
|
|
}
|
|
|
|
static int mt9m021_start_streaming(struct tegracam_device *tc_dev)
|
|
{
|
|
struct mt9m021 *priv = (struct mt9m021 *)tegracam_get_privdata(tc_dev);
|
|
struct camera_common_data *s_data = priv->s_data;
|
|
|
|
dev_info(tc_dev->dev, "Starting stream\n");
|
|
|
|
if (strstr(priv->trigger_mode, "slave") != NULL)
|
|
return mt9m021_write_reg16(s_data, MT9M021_RESET_REG,
|
|
MT9M021_TRIGGER_MODE);
|
|
else
|
|
return mt9m021_write_reg16(s_data, MT9M021_RESET_REG,
|
|
MT9M021_MASTER_MODE);
|
|
}
|
|
|
|
static int mt9m021_stop_streaming(struct tegracam_device *tc_dev)
|
|
{
|
|
struct mt9m021 *priv = (struct mt9m021 *)tegracam_get_privdata(tc_dev);
|
|
struct device *dev = tc_dev->dev;
|
|
int err;
|
|
|
|
dev_info(dev, "Ending stream\n");
|
|
|
|
err = mt9m021_write_table(priv, mode_table[MT9M021_MODE_STOP_STREAM]);
|
|
if (err)
|
|
return err;
|
|
|
|
usleep_range(50000, 51000);
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct camera_common_sensor_ops mt9m021_common_ops = {
|
|
.numfrmfmts = ARRAY_SIZE(mt9m021_frmfmt),
|
|
.frmfmt_table = mt9m021_frmfmt,
|
|
.power_on = mt9m021_power_on,
|
|
.power_off = mt9m021_power_off,
|
|
.write_reg = mt9m021_write_reg,
|
|
.read_reg = mt9m021_read_reg,
|
|
.parse_dt = mt9m021_parse_dt,
|
|
.power_get = mt9m021_power_get,
|
|
.power_put = mt9m021_power_put,
|
|
.set_mode = mt9m021_set_mode,
|
|
.start_streaming = mt9m021_start_streaming,
|
|
.stop_streaming = mt9m021_stop_streaming,
|
|
};
|
|
|
|
static int mt9m021_get_trigger_mode(struct mt9m021 *priv)
|
|
{
|
|
int err = 0;
|
|
struct i2c_client *client = priv->i2c_client;
|
|
struct device_node *node = client->dev.of_node;
|
|
const struct of_device_id *match;
|
|
|
|
dev_dbg(&client->dev, "%s\n", __func__);
|
|
|
|
if (!node)
|
|
return -EFAULT;
|
|
|
|
match = of_match_device(mt9m021_of_match, &client->dev);
|
|
if (!match) {
|
|
dev_err(&client->dev, "Failed to find matching dt id\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
err =
|
|
of_property_read_string(node, "trigger_mode", &priv->trigger_mode);
|
|
if (err == -EINVAL) {
|
|
dev_warn(&client->dev, "trigger_mode not in device tree\n");
|
|
*(&priv->trigger_mode) = "master";
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_verify_chip_id(struct mt9m021 *priv)
|
|
{
|
|
struct i2c_client *client;
|
|
struct camera_common_data *s_data;
|
|
int max_retries = MT9M021_MAX_RETRIES;
|
|
u16 chip_id = 0;
|
|
int err;
|
|
int ret;
|
|
|
|
if (!priv)
|
|
return -EFAULT;
|
|
|
|
client = priv->i2c_client;
|
|
s_data = priv->s_data;
|
|
|
|
err = mt9m021_power_on(s_data);
|
|
if (err)
|
|
goto exit;
|
|
|
|
while (max_retries) {
|
|
err = mt9m021_read_reg16(s_data, MT9M021_CHIP_ID_REG, &chip_id);
|
|
if (!err)
|
|
break;
|
|
dev_info(&client->dev, "Failed to read Chip ID, trying again\n");
|
|
max_retries--;
|
|
msleep(30);
|
|
}
|
|
if (err) {
|
|
dev_err(&client->dev, "Failed to read Chip ID\n");
|
|
goto exit;
|
|
}
|
|
dev_info(&client->dev, "Read Chip ID 0x%04x\n", chip_id);
|
|
|
|
if (chip_id != MT9M021_CHIP_ID) {
|
|
dev_err(&client->dev, "Read unknown chip ID 0x%04x\n", chip_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
ret = mt9m021_power_off(s_data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt9m021_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
|
|
{
|
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
|
|
|
dev_dbg(&client->dev, "%s:\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_internal_ops mt9m021_subdev_internal_ops = {
|
|
.open = mt9m021_open,
|
|
};
|
|
|
|
static int mt9m021_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct device *dev = &client->dev;
|
|
struct tegracam_device *tc_dev;
|
|
struct mt9m021 *priv;
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
int err;
|
|
|
|
dev_dbg(dev, "probing v4l2 sensor at addr 0x%0x\n", client->addr);
|
|
|
|
if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node)
|
|
return -EINVAL;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(struct mt9m021), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
tc_dev = devm_kzalloc(dev, sizeof(struct tegracam_device), GFP_KERNEL);
|
|
if (!tc_dev)
|
|
return -ENOMEM;
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
|
|
dev_err(&client->dev,
|
|
"i2c-adapter doesn't support I2C_FUNC_SMBUS_WORD\n");
|
|
return -EIO;
|
|
}
|
|
|
|
priv->i2c_client = tc_dev->client = client;
|
|
tc_dev->dev = dev;
|
|
strncpy(tc_dev->name, "mt9m021", sizeof(tc_dev->name));
|
|
tc_dev->dev_regmap_config = &sensor_regmap_config;
|
|
tc_dev->sensor_ops = &mt9m021_common_ops;
|
|
tc_dev->v4l2sd_internal_ops = &mt9m021_subdev_internal_ops;
|
|
tc_dev->tcctrl_ops = &mt9m021_ctrl_ops;
|
|
|
|
err = tegracam_device_register(tc_dev);
|
|
if (err) {
|
|
dev_err(dev, "tegra camera driver registration failed\n");
|
|
return err;
|
|
}
|
|
priv->tc_dev = tc_dev;
|
|
priv->s_data = tc_dev->s_data;
|
|
priv->subdev = &tc_dev->s_data->subdev;
|
|
tegracam_set_privdata(tc_dev, (void *)priv);
|
|
priv->flash_en = true;
|
|
|
|
err = mt9m021_verify_chip_id(priv);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mt9m021_get_trigger_mode(priv);
|
|
if (err)
|
|
return err;
|
|
|
|
if (strstr(priv->trigger_mode, "slave") != NULL)
|
|
dev_info(dev, "slave mode activated\n");
|
|
|
|
err = tegracam_v4l2subdev_register(tc_dev, true);
|
|
if (err) {
|
|
dev_err(dev, "tegra camera subdev registration failed\n");
|
|
return err;
|
|
}
|
|
|
|
dev_dbg(dev, "detected mt9m021 sensor\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt9m021_remove(struct i2c_client *client)
|
|
{
|
|
struct camera_common_data *s_data = to_camera_common_data(&client->dev);
|
|
struct mt9m021 *priv = (struct mt9m021 *)s_data->priv;
|
|
|
|
tegracam_v4l2subdev_unregister(priv->tc_dev);
|
|
tegracam_device_unregister(priv->tc_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id mt9m021_id[] = {
|
|
{"mt9m021", 0},
|
|
{}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, mt9m021_id);
|
|
|
|
static struct i2c_driver mt9m021_i2c_driver = {
|
|
.driver = {
|
|
.name = "mt9m021",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(mt9m021_of_match),
|
|
},
|
|
.probe = mt9m021_probe,
|
|
.remove = mt9m021_remove,
|
|
.id_table = mt9m021_id,
|
|
};
|
|
|
|
module_i2c_driver(mt9m021_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("Media Controller driver for Sony MT9M021");
|
|
MODULE_AUTHOR("Enrique Ramirez <support@ridgerun.com>");
|
|
MODULE_LICENSE("GPL v2");
|