Created
May 25, 2023 06:31
-
-
Save will127534/f734a32b18b44d57448b4e46d07bb1c7 to your computer and use it in GitHub Desktop.
imx585 v4l2 driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-2.0 | |
/* | |
* A V4L2 driver for Sony imx585 cameras. | |
* | |
* Based on Sony imx477 camera driver | |
* Copyright (C) 2019-2020 Raspberry Pi (Trading) Ltd | |
*/ | |
#include <asm/unaligned.h> | |
#include <linux/clk.h> | |
#include <linux/delay.h> | |
#include <linux/gpio/consumer.h> | |
#include <linux/i2c.h> | |
#include <linux/module.h> | |
#include <linux/of_device.h> | |
#include <linux/pm_runtime.h> | |
#include <linux/regulator/consumer.h> | |
#include <media/v4l2-ctrls.h> | |
#include <media/v4l2-device.h> | |
#include <media/v4l2-event.h> | |
#include <media/v4l2-fwnode.h> | |
#include <media/v4l2-mediabus.h> | |
/* Chip ID */ | |
#define IMX585_REG_CHIP_ID 0x3000 | |
#define IMX585_CHIP_ID 0x0000 | |
#define IMX585_REG_MODE_SELECT 0x3000 | |
#define IMX585_MODE_STANDBY 0x01 | |
#define IMX585_MODE_STREAMING 0x00 | |
#define IMX585_XCLK_FREQ 24000000 | |
/* VMAX internal VBLANK*/ | |
#define IMX585_REG_VMAX 0x3028 | |
#define IMX585_VMAX_MAX 0xfffff | |
/* HMAX internal HBLANK*/ | |
#define IMX585_REG_HMAX 0x302C | |
#define IMX585_HMAX_MAX 0xffff | |
/* SHR internal */ | |
#define IMX585_REG_SHR 0x3050 | |
#define IMX585_SHR_MIN 11 | |
/* Exposure control */ | |
#define IMX585_EXPOSURE_MIN 52 | |
#define IMX585_EXPOSURE_STEP 1 | |
#define IMX585_EXPOSURE_DEFAULT 1000 | |
#define IMX585_EXPOSURE_MAX 49865 | |
/* Analog gain control */ | |
#define IMX585_REG_ANALOG_GAIN 0x306C | |
#define IMX585_ANA_GAIN_MIN 0 | |
#define IMX585_ANA_GAIN_MAX 1 | |
#define IMX585_ANA_GAIN_STEP 1 | |
#define IMX585_ANA_GAIN_DEFAULT 0x0 | |
#define IMX585_REG_VFLIP 0x3021 | |
/* Embedded metadata stream structure */ | |
#define IMX585_EMBEDDED_LINE_WIDTH 16384 | |
#define IMX585_NUM_EMBEDDED_LINES 1 | |
enum pad_types { | |
IMAGE_PAD, | |
METADATA_PAD, | |
NUM_PADS | |
}; | |
/* imx585 native and active pixel array size. */ | |
#define IMX585_NATIVE_WIDTH 3856U | |
#define IMX585_NATIVE_HEIGHT 2180U | |
#define imx585_PIXEL_ARRAY_LEFT 8U | |
#define imx585_PIXEL_ARRAY_TOP 4U | |
#define imx585_PIXEL_ARRAY_WIDTH 3840U | |
#define imx585_PIXEL_ARRAY_HEIGHT 2160U | |
struct imx585_reg { | |
u16 address; | |
u8 val; | |
}; | |
struct IMX585_reg_list { | |
unsigned int num_of_regs; | |
const struct imx585_reg *regs; | |
}; | |
/* Mode : resolution and related config&values */ | |
struct imx585_mode { | |
/* Frame width */ | |
unsigned int width; | |
/* Frame height */ | |
unsigned int height; | |
/* minimum H-timing */ | |
uint64_t min_HMAX; | |
/* minimum V-timing */ | |
uint64_t min_VMAX; | |
/* default H-timing */ | |
uint64_t default_HMAX; | |
/* default V-timing */ | |
uint64_t default_VMAX; | |
/* minimum SHR */ | |
uint64_t min_SHR; | |
/* Analog crop rectangle. */ | |
struct v4l2_rect crop; | |
/* Default register values */ | |
struct IMX585_reg_list reg_list; | |
}; | |
static const struct imx585_reg mode_common_regs[] = { | |
{0x3000, 0x01}, //standby | |
{0x3002, 0x01}, | |
{0x301A, 0x10}, //WDMODE | |
{0x3024, 0x02}, | |
{0x3069, 0x02}, | |
{0x3074, 0x63}, | |
{0x3930, 0xE6},//DUR | |
{0x3A4C, 0x61},// WAIT_ST0 | |
{0x3A4D, 0x02},// | |
{0x3A50, 0x70},// WAIT_ST1 | |
{0x3A51, 0x02},// | |
{0x3E10, 0x17},// ADTHEN | |
{0x493C, 0x23},// ADTHEN | |
{0x4940, 0x41},// ADTHEN | |
{0x3014, 0x04},// INCK_SEL [3:0] 24 MHz | |
{0x3015, 0x03},// DATARATE_SEL [3:0] 1440 Mbps | |
{0x302C, 0x4C},// HMAX [15:0] | |
{0x302D, 0x04},// | |
{0x3030, 0x01},// | |
{0x3040, 0x02},// LANEMODE [2:0] 4 lane | |
{0x3023, 0x01},// RAW12 | |
{0x3028, 0x94},// VMAX | |
{0x3029, 0x11},// VMAX | |
{0x3050, 0xFF},// SHR0 [19:0] | |
{0x30A6, 0x00},// XVS_DRV [1:0] | |
{0x3460, 0x21},// - | |
{0x3478, 0xA1},// - | |
{0x347C, 0x01},// - | |
{0x3480, 0x01},// - | |
//{0x3930, 0x0C},// DUR | |
//{0x3931, 0x01},// | |
//{0x3A4C, 0x39},// WAIT_ST0 | |
//{0x3A4D, 0x01},// | |
{0x3A4E, 0x14},// - | |
//{0x3A50, 0x48},// WAIT_ST1 | |
//{0x3A51, 0x01},// | |
{0x3A52, 0x14},// - | |
{0x3A56, 0x00},// - | |
{0x3A5A, 0x00},// - | |
{0x3A5E, 0x00},// - | |
{0x3A62, 0x00},// - | |
{0x3A6A, 0x20},// - | |
{0x3A6C, 0x42},// - | |
{0x3A6E, 0xA0},// - | |
{0x3B2C, 0x0C},// - | |
{0x3B30, 0x1C},// - | |
{0x3B34, 0x0C},// - | |
{0x3B38, 0x1C},// - | |
{0x3BA0, 0x0C},// - | |
{0x3BA4, 0x1C},// - | |
{0x3BA8, 0x0C},// - | |
{0x3BAC, 0x1C},// - | |
{0x3D3C, 0x11},// - | |
{0x3D46, 0x0B},// - | |
{0x3DE0, 0x3F},// - | |
{0x3DE1, 0x08},// - | |
//{0x3E10, 0x10},// ADTHEN [2:0] | |
{0x3E14, 0x87},// - | |
{0x3E16, 0x91},// - | |
{0x3E18, 0x91},// - | |
{0x3E1A, 0x87},// - | |
{0x3E1C, 0x78},// - | |
{0x3E1E, 0x50},// - | |
{0x3E20, 0x50},// - | |
{0x3E22, 0x50},// - | |
{0x3E24, 0x87},// - | |
{0x3E26, 0x91},// - | |
{0x3E28, 0x91},// - | |
{0x3E2A, 0x87},// - | |
{0x3E2C, 0x78},// - | |
{0x3E2E, 0x50},// - | |
{0x3E30, 0x50},// - | |
{0x3E32, 0x50},// - | |
{0x3E34, 0x87},// - | |
{0x3E36, 0x91},// - | |
{0x3E38, 0x91},// - | |
{0x3E3A, 0x87},// - | |
{0x3E3C, 0x78},// - | |
{0x3E3E, 0x50},// - | |
{0x3E40, 0x50},// - | |
{0x3E42, 0x50},// - | |
{0x4054, 0x64},// - | |
{0x4148, 0xFE},// - | |
{0x4149, 0x05},// - | |
{0x414A, 0xFF},// - | |
{0x414B, 0x05},// - | |
{0x420A, 0x03},// - | |
{0x4231, 0x08},// - | |
{0x423D, 0x9C},// - | |
{0x4242, 0xB4},// - | |
{0x4246, 0xB4},// - | |
{0x424E, 0xB4},// - | |
{0x425C, 0xB4},// - | |
{0x425E, 0xB6},// - | |
{0x426C, 0xB4},// - | |
{0x426E, 0xB6},// - | |
{0x428C, 0xB4},// - | |
{0x428E, 0xB6},// - | |
{0x4708, 0x00},// - | |
{0x4709, 0x00},// - | |
{0x470A, 0xFF},// - | |
{0x470B, 0x03},// - | |
{0x470C, 0x00},// - | |
{0x470D, 0x00},// - | |
{0x470E, 0xFF},// - | |
{0x470F, 0x03},// - | |
{0x47EB, 0x1C},// - | |
{0x47F0, 0xA6},// - | |
{0x47F2, 0xA6},// - | |
{0x47F4, 0xA0},// - | |
{0x47F6, 0x96},// - | |
{0x4808, 0xA6},// - | |
{0x480A, 0xA6},// - | |
{0x480C, 0xA0},// - | |
{0x480E, 0x96},// - | |
{0x492C, 0xB2},// - | |
{0x4930, 0x03},// - | |
{0x4932, 0x03},// - | |
{0x4936, 0x5B},// - | |
{0x4938, 0x82},// - | |
//{0x493C, 0x23},// WAIT_10_SHF | |
{0x493E, 0x23},// - | |
//{0x4940, 0x23},// WAIT_12_SHF | |
{0x4BA8, 0x1C},// - | |
{0x4BA9, 0x03},// - | |
{0x4BAC, 0x1C},// - | |
{0x4BAD, 0x1C},// - | |
{0x4BAE, 0x1C},// - | |
{0x4BAF, 0x1C},// - | |
{0x4BB0, 0x1C},// - | |
{0x4BB1, 0x1C},// - | |
{0x4BB2, 0x1C},// - | |
{0x4BB3, 0x1C},// - | |
{0x4BB4, 0x1C},// - | |
{0x4BB8, 0x03},// - | |
{0x4BB9, 0x03},// - | |
{0x4BBA, 0x03},// - | |
{0x4BBB, 0x03},// - | |
{0x4BBC, 0x03},// - | |
{0x4BBD, 0x03},// - | |
{0x4BBE, 0x03},// - | |
{0x4BBF, 0x03},// - | |
{0x4BC0, 0x03},// - | |
{0x4C14, 0x87},// - | |
{0x4C16, 0x91},// - | |
{0x4C18, 0x91},// - | |
{0x4C1A, 0x87},// - | |
{0x4C1C, 0x78},// - | |
{0x4C1E, 0x50},// - | |
{0x4C20, 0x50},// - | |
{0x4C22, 0x50},// - | |
{0x4C24, 0x87},// - | |
{0x4C26, 0x91},// - | |
{0x4C28, 0x91},// - | |
{0x4C2A, 0x87},// - | |
{0x4C2C, 0x78},// - | |
{0x4C2E, 0x50},// - | |
{0x4C30, 0x50},// - | |
{0x4C32, 0x50},// - | |
{0x4C34, 0x87},// - | |
{0x4C36, 0x91},// - | |
{0x4C38, 0x91},// - | |
{0x4C3A, 0x87},// - | |
{0x4C3C, 0x78},// - | |
{0x4C3E, 0x50},// - | |
{0x4C40, 0x50},// - | |
{0x4C42, 0x50},// - | |
{0x4D12, 0x1F},// - | |
{0x4D13, 0x1E},// - | |
{0x4D26, 0x33},// - | |
{0x4E0E, 0x59},// - | |
{0x4E14, 0x55},// - | |
{0x4E16, 0x59},// - | |
{0x4E1E, 0x3B},// - | |
{0x4E20, 0x47},// - | |
{0x4E22, 0x54},// - | |
{0x4E26, 0x81},// - | |
{0x4E2C, 0x7D},// - | |
{0x4E2E, 0x81},// - | |
{0x4E36, 0x63},// - | |
{0x4E38, 0x6F},// - | |
{0x4E3A, 0x7C},// - | |
{0x4F3A, 0x3C},// - | |
{0x4F3C, 0x46},// - | |
{0x4F3E, 0x59},// - | |
{0x4F42, 0x64},// - | |
{0x4F44, 0x6E},// - | |
{0x4F46, 0x81},// - | |
{0x4F4A, 0x82},// - | |
{0x4F5A, 0x81},// - | |
{0x4F62, 0xAA},// - | |
{0x4F72, 0xA9},// - | |
{0x4F78, 0x36},// - | |
{0x4F7A, 0x41},// - | |
{0x4F7C, 0x61},// - | |
{0x4F7D, 0x01},// - | |
{0x4F7E, 0x7C},// - | |
{0x4F7F, 0x01},// - | |
{0x4F80, 0x77},// - | |
{0x4F82, 0x7B},// - | |
{0x4F88, 0x37},// - | |
{0x4F8A, 0x40},// - | |
{0x4F8C, 0x62},// - | |
{0x4F8D, 0x01},// - | |
{0x4F8E, 0x76},// - | |
{0x4F8F, 0x01},// - | |
{0x4F90, 0x5E},// - | |
{0x4F91, 0x02},// - | |
{0x4F92, 0x69},// - | |
{0x4F93, 0x02},// - | |
{0x4F94, 0x89},// - | |
{0x4F95, 0x02},// - | |
{0x4F96, 0xA4},// - | |
{0x4F97, 0x02},// - | |
{0x4F98, 0x9F},// - | |
{0x4F99, 0x02},// - | |
{0x4F9A, 0xA3},// - | |
{0x4F9B, 0x02},// - | |
{0x4FA0, 0x5F},// - | |
{0x4FA1, 0x02},// - | |
{0x4FA2, 0x68},// - | |
{0x4FA3, 0x02},// - | |
{0x4FA4, 0x8A},// - | |
{0x4FA5, 0x02},// - | |
{0x4FA6, 0x9E},// - | |
{0x4FA7, 0x02},// - | |
{0x519E, 0x79},// - | |
{0x51A6, 0xA1},// - | |
{0x51F0, 0xAC},// - | |
{0x51F2, 0xAA},// - | |
{0x51F4, 0xA5},// - | |
{0x51F6, 0xA0},// - | |
{0x5200, 0x9B},// - | |
{0x5202, 0x91},// - | |
{0x5204, 0x87},// - | |
{0x5206, 0x82},// - | |
{0x5208, 0xAC},// - | |
{0x520A, 0xAA},// - | |
{0x520C, 0xA5},// - | |
{0x520E, 0xA0},// - | |
{0x5210, 0x9B},// - | |
{0x5212, 0x91},// - | |
{0x5214, 0x87},// - | |
{0x5216, 0x82},// - | |
{0x5218, 0xAC},// - | |
{0x521A, 0xAA},// - | |
{0x521C, 0xA5},// - | |
{0x521E, 0xA0},// - | |
{0x5220, 0x9B},// - | |
{0x5222, 0x91},// - | |
{0x5224, 0x87},// - | |
{0x5226, 0x82},// - | |
{0x3000, 0x00}, //standby Cancel | |
{0xFFFE, 0x19}, | |
{0x3002, 0x00}, | |
}; | |
/* 20MPix 20fps readout mode 0 */ | |
static const struct imx585_reg mode_4k_regs[] = { | |
}; | |
/* Mode configs */ | |
static const struct imx585_mode supported_modes_12bit[] = { | |
{ | |
/* 20MPix 20fps readout mode 0 */ | |
.width = 3856, | |
.height = 2180, | |
.min_HMAX = 550, | |
.min_VMAX = 4500, | |
.default_HMAX = 550, | |
.default_VMAX = 4500, | |
.min_SHR = 20, | |
.crop = { | |
.left = imx585_PIXEL_ARRAY_LEFT, | |
.top = imx585_PIXEL_ARRAY_TOP, | |
.width = imx585_PIXEL_ARRAY_WIDTH, | |
.height = imx585_PIXEL_ARRAY_HEIGHT, | |
}, | |
.reg_list = { | |
.num_of_regs = ARRAY_SIZE(mode_4k_regs), | |
.regs = mode_4k_regs, | |
}, | |
}, | |
}; | |
/* | |
* The supported formats. | |
* This table MUST contain 4 entries per format, to cover the various flip | |
* combinations in the order | |
* - no flip | |
* - h flip | |
* - v flip | |
* - h&v flips | |
*/ | |
static const u32 codes[] = { | |
/* 12-bit modes. */ | |
MEDIA_BUS_FMT_SRGGB12_1X12, | |
MEDIA_BUS_FMT_SGRBG12_1X12, | |
MEDIA_BUS_FMT_SGBRG12_1X12, | |
MEDIA_BUS_FMT_SBGGR12_1X12, | |
}; | |
/* regulator supplies */ | |
static const char * const imx585_supply_name[] = { | |
/* Supplies can be enabled in any order */ | |
"VANA", /* Analog (2.8V) supply */ | |
"VDIG", /* Digital Core (1.05V) supply */ | |
"VDDL", /* IF (1.8V) supply */ | |
}; | |
#define imx585_NUM_SUPPLIES ARRAY_SIZE(imx585_supply_name) | |
/* | |
* Initialisation delay between XCLR low->high and the moment when the sensor | |
* can start capture (i.e. can leave software standby), given by T7 in the | |
* datasheet is 8ms. This does include I2C setup time as well. | |
* | |
* Note, that delay between XCLR low->high and reading the CCI ID register (T6 | |
* in the datasheet) is much smaller - 600us. | |
*/ | |
#define imx585_XCLR_MIN_DELAY_US 100000 | |
#define imx585_XCLR_DELAY_RANGE_US 1000 | |
struct imx585_compatible_data { | |
unsigned int chip_id; | |
struct IMX585_reg_list extra_regs; | |
}; | |
struct imx585 { | |
struct v4l2_subdev sd; | |
struct media_pad pad[NUM_PADS]; | |
unsigned int fmt_code; | |
struct clk *xclk; | |
u32 xclk_freq; | |
struct gpio_desc *reset_gpio; | |
struct regulator_bulk_data supplies[imx585_NUM_SUPPLIES]; | |
struct v4l2_ctrl_handler ctrl_handler; | |
/* V4L2 Controls */ | |
struct v4l2_ctrl *pixel_rate; | |
struct v4l2_ctrl *exposure; | |
struct v4l2_ctrl *vflip; | |
struct v4l2_ctrl *hflip; | |
struct v4l2_ctrl *vblank; | |
struct v4l2_ctrl *hblank; | |
/* Current mode */ | |
const struct imx585_mode *mode; | |
uint16_t HMAX; | |
uint32_t VMAX; | |
/* | |
* Mutex for serialized access: | |
* Protect sensor module set pad format and start/stop streaming safely. | |
*/ | |
struct mutex mutex; | |
/* Streaming on/off */ | |
bool streaming; | |
/* Rewrite common registers on stream on? */ | |
bool common_regs_written; | |
/* Any extra information related to different compatible sensors */ | |
const struct imx585_compatible_data *compatible_data; | |
}; | |
static inline struct imx585 *to_imx585(struct v4l2_subdev *_sd) | |
{ | |
return container_of(_sd, struct imx585, sd); | |
} | |
static inline void get_mode_table(unsigned int code, | |
const struct imx585_mode **mode_list, | |
unsigned int *num_modes) | |
{ | |
switch (code) { | |
/* 12-bit */ | |
case MEDIA_BUS_FMT_SRGGB12_1X12: | |
case MEDIA_BUS_FMT_SGRBG12_1X12: | |
case MEDIA_BUS_FMT_SGBRG12_1X12: | |
case MEDIA_BUS_FMT_SBGGR12_1X12: | |
*mode_list = supported_modes_12bit; | |
*num_modes = ARRAY_SIZE(supported_modes_12bit); | |
break; | |
default: | |
*mode_list = NULL; | |
*num_modes = 0; | |
} | |
} | |
/* Read registers up to 2 at a time */ | |
static int imx585_read_reg(struct imx585 *imx585, u16 reg, u32 len, u32 *val) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
struct i2c_msg msgs[2]; | |
u8 addr_buf[2] = { reg >> 8, reg & 0xff }; | |
u8 data_buf[4] = { 0, }; | |
int ret; | |
if (len > 4) | |
return -EINVAL; | |
/* Write register address */ | |
msgs[0].addr = client->addr; | |
msgs[0].flags = 0; | |
msgs[0].len = ARRAY_SIZE(addr_buf); | |
msgs[0].buf = addr_buf; | |
/* Read data from register */ | |
msgs[1].addr = client->addr; | |
msgs[1].flags = I2C_M_RD; | |
msgs[1].len = len; | |
msgs[1].buf = &data_buf[4 - len]; | |
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); | |
if (ret != ARRAY_SIZE(msgs)) | |
return -EIO; | |
*val = get_unaligned_be32(data_buf); | |
return 0; | |
} | |
/* Write registers 1 byte at a time */ | |
static int imx585_write_reg_1byte(struct imx585 *imx585, u16 reg, u8 val) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
u8 buf[3]; | |
put_unaligned_be16(reg, buf); | |
buf[2] = val; | |
if (i2c_master_send(client, buf, 3) != 3) | |
return -EIO; | |
return 0; | |
} | |
/* Write registers 2 byte at a time */ | |
static int imx585_write_reg_2byte(struct imx585 *imx585, u16 reg, u16 val) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
u8 buf[4]; | |
put_unaligned_be16(reg, buf); | |
buf[2] = val; | |
buf[3] = val>>8; | |
if (i2c_master_send(client, buf, 4) != 4) | |
return -EIO; | |
return 0; | |
} | |
/* Write registers 3 byte at a time */ | |
static int imx585_write_reg_3byte(struct imx585 *imx585, u16 reg, u32 val) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
u8 buf[5]; | |
put_unaligned_be16(reg, buf); | |
buf[2] = val; | |
buf[3] = val>>8; | |
buf[4] = val>>16; | |
if (i2c_master_send(client, buf, 5) != 5) | |
return -EIO; | |
return 0; | |
} | |
/* Write a list of 1 byte registers */ | |
static int imx585_write_regs(struct imx585 *imx585, | |
const struct imx585_reg *regs, u32 len) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
unsigned int i; | |
int ret; | |
for (i = 0; i < len; i++) { | |
if (regs[i].address == 0xFFFE) { | |
usleep_range(regs[i].val*1000,(regs[i].val+1)*1000); | |
} | |
else{ | |
ret = imx585_write_reg_1byte(imx585, regs[i].address, regs[i].val); | |
if (ret) { | |
dev_err_ratelimited(&client->dev, | |
"Failed to write reg 0x%4.4x. error = %d\n", | |
regs[i].address, ret); | |
return ret; | |
} | |
} | |
} | |
return 0; | |
} | |
/* Get bayer order based on flip setting. */ | |
static u32 imx585_get_format_code(struct imx585 *imx585, u32 code) | |
{ | |
unsigned int i; | |
lockdep_assert_held(&imx585->mutex); | |
for (i = 0; i < ARRAY_SIZE(codes); i++) | |
if (codes[i] == code) | |
break; | |
return codes[i]; | |
} | |
static void imx585_set_default_format(struct imx585 *imx585) | |
{ | |
/* Set default mode to max resolution */ | |
imx585->mode = &supported_modes_12bit[0]; | |
imx585->fmt_code = MEDIA_BUS_FMT_SRGGB12_1X12; | |
} | |
static int imx585_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) | |
{ | |
struct imx585 *imx585 = to_imx585(sd); | |
struct v4l2_mbus_framefmt *try_fmt_img = | |
v4l2_subdev_get_try_format(sd, fh->state, IMAGE_PAD); | |
struct v4l2_mbus_framefmt *try_fmt_meta = | |
v4l2_subdev_get_try_format(sd, fh->state, METADATA_PAD); | |
struct v4l2_rect *try_crop; | |
mutex_lock(&imx585->mutex); | |
/* Initialize try_fmt for the image pad */ | |
try_fmt_img->width = supported_modes_12bit[0].width; | |
try_fmt_img->height = supported_modes_12bit[0].height; | |
try_fmt_img->code = imx585_get_format_code(imx585, | |
MEDIA_BUS_FMT_SRGGB12_1X12); | |
try_fmt_img->field = V4L2_FIELD_NONE; | |
/* Initialize try_fmt for the embedded metadata pad */ | |
try_fmt_meta->width = IMX585_EMBEDDED_LINE_WIDTH; | |
try_fmt_meta->height = IMX585_NUM_EMBEDDED_LINES; | |
try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; | |
try_fmt_meta->field = V4L2_FIELD_NONE; | |
/* Initialize try_crop */ | |
try_crop = v4l2_subdev_get_try_crop(sd, fh->state, IMAGE_PAD); | |
try_crop->left = imx585_PIXEL_ARRAY_LEFT; | |
try_crop->top = imx585_PIXEL_ARRAY_TOP; | |
try_crop->width = imx585_PIXEL_ARRAY_WIDTH; | |
try_crop->height = imx585_PIXEL_ARRAY_HEIGHT; | |
mutex_unlock(&imx585->mutex); | |
return 0; | |
} | |
static u64 calculate_v4l2_cid_exposure(u64 hmax, u64 vmax, u64 shr, u64 svr, u64 offset) { | |
u64 numerator; | |
numerator = (vmax * (svr + 1) - shr) * hmax + offset; | |
do_div(numerator, hmax); | |
numerator = clamp_t(uint32_t, numerator, 0, 0xFFFFFFFF); | |
return numerator; | |
} | |
static void calculate_min_max_v4l2_cid_exposure(u64 hmax, u64 vmax, u64 min_shr, u64 svr, u64 offset, u64 *min_exposure, u64 *max_exposure) { | |
u64 max_shr = (svr + 1) * vmax - 4; | |
max_shr = min_t(uint64_t, max_shr, 0xFFFF); | |
*min_exposure = calculate_v4l2_cid_exposure(hmax, vmax, max_shr, svr, offset); | |
*max_exposure = calculate_v4l2_cid_exposure(hmax, vmax, min_shr, svr, offset); | |
} | |
/* | |
Integration Time [s] = [{VMAX × (SVR + 1) – (SHR)} | |
× HMAX + offset] / (72 × 10^6) | |
Integration Time [s] = exposure * HMAX / (72 × 10^6) | |
*/ | |
static uint32_t calculate_shr(uint32_t exposure, uint32_t hmax, uint64_t vmax, uint32_t svr, uint32_t offset) { | |
uint64_t temp; | |
uint32_t shr; | |
temp = ((uint64_t)exposure * hmax - offset); | |
do_div(temp, hmax); | |
shr = (uint32_t)(vmax * (svr + 1) - temp); | |
return shr; | |
} | |
static int imx585_set_ctrl(struct v4l2_ctrl *ctrl) | |
{ | |
struct imx585 *imx585 = | |
container_of(ctrl->handler, struct imx585, ctrl_handler); | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
const struct imx585_mode *mode = imx585->mode; | |
int ret = 0; | |
/* | |
* The VBLANK control may change the limits of usable exposure, so check | |
* and adjust if necessary. | |
*/ | |
if (ctrl->id == V4L2_CID_VBLANK){ | |
/* Honour the VBLANK limits when setting exposure. */ | |
u64 current_exposure, max_exposure, min_exposure, vmax; | |
vmax = ((u64)mode->height + ctrl->val) ; | |
imx585 -> VMAX = vmax; | |
calculate_min_max_v4l2_cid_exposure(imx585 -> HMAX, imx585 -> VMAX, (u64)mode->min_SHR, 0, 209, &min_exposure, &max_exposure); | |
current_exposure = clamp_t(uint32_t, current_exposure, min_exposure, max_exposure); | |
dev_info(&client->dev,"exposure_max:%lld, exposure_min:%lld, current_exposure:%lld\n",max_exposure, min_exposure, current_exposure); | |
dev_info(&client->dev,"\tVMAX:%d, HMAX:%d\n",imx585->VMAX, imx585->HMAX); | |
__v4l2_ctrl_modify_range(imx585->exposure, min_exposure,max_exposure, 1,current_exposure); | |
} | |
/* | |
* Applying V4L2 control value only happens | |
* when power is up for streaming | |
*/ | |
if (pm_runtime_get_if_in_use(&client->dev) == 0) | |
return 0; | |
switch (ctrl->id) { | |
case V4L2_CID_EXPOSURE: | |
{ | |
dev_info(&client->dev,"V4L2_CID_EXPOSURE : %d\n",ctrl->val); | |
dev_info(&client->dev,"\tvblank:%d, hblank:%d\n",imx585->vblank->val, imx585->hblank->val); | |
dev_info(&client->dev,"\tVMAX:%d, HMAX:%d\n",imx585->VMAX, imx585->HMAX); | |
u64 shr = calculate_shr(ctrl->val, imx585->HMAX, imx585->VMAX, 0, 209); | |
dev_info(&client->dev,"\tSHR:%lld\n",shr); | |
ret = imx585_write_reg_2byte(imx585, IMX585_REG_SHR, shr); | |
} | |
break; | |
case V4L2_CID_ANALOGUE_GAIN: | |
dev_info(&client->dev,"V4L2_CID_ANALOGUE_GAIN : %d\n",ctrl->val); | |
ret = imx585_write_reg_2byte(imx585, IMX585_REG_ANALOG_GAIN, ctrl->val); | |
break; | |
case V4L2_CID_VBLANK: | |
{ | |
dev_info(&client->dev,"V4L2_CID_VBLANK : %d\n",ctrl->val); | |
imx585 -> VMAX = ((u64)mode->height + ctrl->val) ; | |
dev_info(&client->dev,"\tVMAX : %d\n",imx585 -> VMAX); | |
ret = imx585_write_reg_3byte(imx585, IMX585_REG_VMAX, imx585 -> VMAX); | |
} | |
break; | |
case V4L2_CID_HBLANK: | |
{ | |
dev_info(&client->dev,"V4L2_CID_HBLANK : %d\n",ctrl->val); | |
//int hmax = (IMX585_NATIVE_WIDTH + ctrl->val) * 72000000; / IMX585_PIXEL_RATE; | |
u64 pixel_rate = (u64)mode->width * 74500000; | |
do_div(pixel_rate,mode->min_HMAX); | |
u64 hmax = (u64)(mode->width + ctrl->val) * 74500000; | |
do_div(hmax,pixel_rate); | |
imx585 -> HMAX = hmax; | |
dev_info(&client->dev,"\tHMAX : %d\n",imx585 -> HMAX); | |
ret = imx585_write_reg_2byte(imx585, IMX585_REG_HMAX, hmax); | |
} | |
break; | |
case V4L2_CID_VFLIP: | |
dev_info(&client->dev,"V4L2_CID_VFLIP : %d\n",imx585->vflip->val); | |
ret = imx585_write_reg_1byte(imx585, IMX585_REG_VFLIP, imx585->vflip->val); | |
break; | |
default: | |
dev_info(&client->dev, | |
"ctrl(id:0x%x,val:0x%x) is not handled\n", | |
ctrl->id, ctrl->val); | |
//ret = -EINVAL; | |
break; | |
} | |
pm_runtime_put(&client->dev); | |
return ret; | |
} | |
static const struct v4l2_ctrl_ops imx585_ctrl_ops = { | |
.s_ctrl = imx585_set_ctrl, | |
}; | |
static int imx585_enum_mbus_code(struct v4l2_subdev *sd, | |
struct v4l2_subdev_state *sd_state, | |
struct v4l2_subdev_mbus_code_enum *code) | |
{ | |
struct imx585 *imx585 = to_imx585(sd); | |
if (code->pad >= NUM_PADS) | |
return -EINVAL; | |
if (code->pad == IMAGE_PAD) { | |
if (code->index >= (ARRAY_SIZE(codes) / 4)) | |
return -EINVAL; | |
code->code = imx585_get_format_code(imx585, | |
codes[code->index * 4]); | |
} else { | |
if (code->index > 0) | |
return -EINVAL; | |
code->code = MEDIA_BUS_FMT_SENSOR_DATA; | |
} | |
return 0; | |
} | |
static int imx585_enum_frame_size(struct v4l2_subdev *sd, | |
struct v4l2_subdev_state *sd_state, | |
struct v4l2_subdev_frame_size_enum *fse) | |
{ | |
struct imx585 *imx585 = to_imx585(sd); | |
if (fse->pad >= NUM_PADS) | |
return -EINVAL; | |
if (fse->pad == IMAGE_PAD) { | |
const struct imx585_mode *mode_list; | |
unsigned int num_modes; | |
get_mode_table(fse->code, &mode_list, &num_modes); | |
if (fse->index >= num_modes) | |
return -EINVAL; | |
if (fse->code != imx585_get_format_code(imx585, fse->code)) | |
return -EINVAL; | |
fse->min_width = mode_list[fse->index].width; | |
fse->max_width = fse->min_width; | |
fse->min_height = mode_list[fse->index].height; | |
fse->max_height = fse->min_height; | |
} else { | |
if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) | |
return -EINVAL; | |
fse->min_width = IMX585_EMBEDDED_LINE_WIDTH; | |
fse->max_width = fse->min_width; | |
fse->min_height = IMX585_NUM_EMBEDDED_LINES; | |
fse->max_height = fse->min_height; | |
} | |
return 0; | |
} | |
static void imx585_reset_colorspace(struct v4l2_mbus_framefmt *fmt) | |
{ | |
fmt->colorspace = V4L2_COLORSPACE_RAW; | |
fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); | |
fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, | |
fmt->colorspace, | |
fmt->ycbcr_enc); | |
fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); | |
} | |
static void imx585_update_image_pad_format(struct imx585 *imx585, | |
const struct imx585_mode *mode, | |
struct v4l2_subdev_format *fmt) | |
{ | |
fmt->format.width = mode->width; | |
fmt->format.height = mode->height; | |
fmt->format.field = V4L2_FIELD_NONE; | |
imx585_reset_colorspace(&fmt->format); | |
} | |
static void imx585_update_metadata_pad_format(struct v4l2_subdev_format *fmt) | |
{ | |
fmt->format.width = IMX585_EMBEDDED_LINE_WIDTH; | |
fmt->format.height = IMX585_NUM_EMBEDDED_LINES; | |
fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; | |
fmt->format.field = V4L2_FIELD_NONE; | |
} | |
static int imx585_get_pad_format(struct v4l2_subdev *sd, | |
struct v4l2_subdev_state *sd_state, | |
struct v4l2_subdev_format *fmt) | |
{ | |
struct imx585 *imx585 = to_imx585(sd); | |
if (fmt->pad >= NUM_PADS) | |
return -EINVAL; | |
mutex_lock(&imx585->mutex); | |
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { | |
struct v4l2_mbus_framefmt *try_fmt = | |
v4l2_subdev_get_try_format(&imx585->sd, sd_state, | |
fmt->pad); | |
/* update the code which could change due to vflip or hflip: */ | |
try_fmt->code = fmt->pad == IMAGE_PAD ? | |
imx585_get_format_code(imx585, try_fmt->code) : | |
MEDIA_BUS_FMT_SENSOR_DATA; | |
fmt->format = *try_fmt; | |
} else { | |
if (fmt->pad == IMAGE_PAD) { | |
imx585_update_image_pad_format(imx585, imx585->mode, | |
fmt); | |
fmt->format.code = | |
imx585_get_format_code(imx585, imx585->fmt_code); | |
} else { | |
imx585_update_metadata_pad_format(fmt); | |
} | |
} | |
mutex_unlock(&imx585->mutex); | |
return 0; | |
} | |
/* TODO */ | |
static void imx585_set_framing_limits(struct imx585 *imx585) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
const struct imx585_mode *mode = imx585->mode; | |
u64 def_hblank; | |
u64 pixel_rate; | |
imx585->VMAX = mode->default_VMAX; | |
imx585->HMAX = mode->default_HMAX; | |
pixel_rate = (u64)mode->width * 74500000; | |
do_div(pixel_rate,mode->min_HMAX); | |
dev_info(&client->dev,"Pixel Rate : %lld\n",pixel_rate); | |
//int def_hblank = mode->default_HMAX * IMX585_PIXEL_RATE / 72000000 - IMX585_NATIVE_WIDTH; | |
def_hblank = mode->default_HMAX * pixel_rate; | |
do_div(def_hblank,74500000); | |
def_hblank = def_hblank - mode->width; | |
__v4l2_ctrl_modify_range(imx585->hblank, 0, | |
IMX585_HMAX_MAX, 1, def_hblank); | |
__v4l2_ctrl_s_ctrl(imx585->hblank, def_hblank); | |
/* Update limits and set FPS to default */ | |
__v4l2_ctrl_modify_range(imx585->vblank, mode->min_VMAX - mode->height, | |
IMX585_VMAX_MAX - mode->height, | |
1, mode->default_VMAX - mode->height); | |
__v4l2_ctrl_s_ctrl(imx585->vblank, mode->default_VMAX - mode->height); | |
/* Setting this will adjust the exposure limits as well. */ | |
__v4l2_ctrl_modify_range(imx585->pixel_rate, pixel_rate, pixel_rate, 1, pixel_rate); | |
dev_info(&client->dev,"Setting default HBLANK : %lld, VBLANK : %lld with PixelRate: %lld\n",def_hblank,mode->default_VMAX - mode->height, pixel_rate); | |
} | |
/* TODO */ | |
static int imx585_set_pad_format(struct v4l2_subdev *sd, | |
struct v4l2_subdev_state *sd_state, | |
struct v4l2_subdev_format *fmt) | |
{ | |
struct v4l2_mbus_framefmt *framefmt; | |
const struct imx585_mode *mode; | |
struct imx585 *imx585 = to_imx585(sd); | |
if (fmt->pad >= NUM_PADS) | |
return -EINVAL; | |
mutex_lock(&imx585->mutex); | |
if (fmt->pad == IMAGE_PAD) { | |
const struct imx585_mode *mode_list; | |
unsigned int num_modes; | |
/* Bayer order varies with flips */ | |
fmt->format.code = imx585_get_format_code(imx585, | |
fmt->format.code); | |
get_mode_table(fmt->format.code, &mode_list, &num_modes); | |
mode = v4l2_find_nearest_size(mode_list, | |
num_modes, | |
width, height, | |
fmt->format.width, | |
fmt->format.height); | |
imx585_update_image_pad_format(imx585, mode, fmt); | |
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { | |
framefmt = v4l2_subdev_get_try_format(sd, sd_state, | |
fmt->pad); | |
*framefmt = fmt->format; | |
} else if (imx585->mode != mode) { | |
imx585->mode = mode; | |
imx585->fmt_code = fmt->format.code; | |
imx585_set_framing_limits(imx585); | |
} | |
} else { | |
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { | |
framefmt = v4l2_subdev_get_try_format(sd, sd_state, | |
fmt->pad); | |
*framefmt = fmt->format; | |
} else { | |
/* Only one embedded data mode is supported */ | |
imx585_update_metadata_pad_format(fmt); | |
} | |
} | |
mutex_unlock(&imx585->mutex); | |
return 0; | |
} | |
/* TODO */ | |
static const struct v4l2_rect * | |
__imx585_get_pad_crop(struct imx585 *imx585, | |
struct v4l2_subdev_state *sd_state, | |
unsigned int pad, enum v4l2_subdev_format_whence which) | |
{ | |
switch (which) { | |
case V4L2_SUBDEV_FORMAT_TRY: | |
return v4l2_subdev_get_try_crop(&imx585->sd, sd_state, pad); | |
case V4L2_SUBDEV_FORMAT_ACTIVE: | |
return &imx585->mode->crop; | |
} | |
return NULL; | |
} | |
/* Start streaming */ | |
static int imx585_start_streaming(struct imx585 *imx585) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
const struct IMX585_reg_list *reg_list; | |
int ret; | |
if (!imx585->common_regs_written) { | |
ret = imx585_write_regs(imx585, mode_common_regs, | |
ARRAY_SIZE(mode_common_regs)); | |
if (ret) { | |
dev_err(&client->dev, "%s failed to set common settings\n", | |
__func__); | |
return ret; | |
} | |
imx585->common_regs_written = true; | |
} | |
/* Apply default values of current mode */ | |
reg_list = &imx585->mode->reg_list; | |
ret = imx585_write_regs(imx585, reg_list->regs, reg_list->num_of_regs); | |
if (ret) { | |
dev_err(&client->dev, "%s failed to set mode\n", __func__); | |
return ret; | |
} | |
/* Apply customized values from user */ | |
ret = __v4l2_ctrl_handler_setup(imx585->sd.ctrl_handler); | |
return ret; | |
} | |
/* Stop streaming */ | |
static void imx585_stop_streaming(struct imx585 *imx585) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
int ret; | |
/* set stream off register */ | |
ret = imx585_write_reg_1byte(imx585, IMX585_REG_MODE_SELECT, IMX585_MODE_STANDBY); | |
if (ret) | |
dev_err(&client->dev, "%s failed to set stream\n", __func__); | |
} | |
static int imx585_set_stream(struct v4l2_subdev *sd, int enable) | |
{ | |
struct imx585 *imx585 = to_imx585(sd); | |
struct i2c_client *client = v4l2_get_subdevdata(sd); | |
int ret = 0; | |
mutex_lock(&imx585->mutex); | |
if (imx585->streaming == enable) { | |
mutex_unlock(&imx585->mutex); | |
return 0; | |
} | |
if (enable) { | |
ret = pm_runtime_get_sync(&client->dev); | |
if (ret < 0) { | |
pm_runtime_put_noidle(&client->dev); | |
goto err_unlock; | |
} | |
/* | |
* Apply default & customized values | |
* and then start streaming. | |
*/ | |
ret = imx585_start_streaming(imx585); | |
if (ret) | |
goto err_rpm_put; | |
} else { | |
imx585_stop_streaming(imx585); | |
pm_runtime_put(&client->dev); | |
} | |
imx585->streaming = enable; | |
/* vflip and hflip cannot change during streaming */ | |
__v4l2_ctrl_grab(imx585->vflip, enable); | |
mutex_unlock(&imx585->mutex); | |
return ret; | |
err_rpm_put: | |
pm_runtime_put(&client->dev); | |
err_unlock: | |
mutex_unlock(&imx585->mutex); | |
return ret; | |
} | |
/* Power/clock management functions */ | |
static int imx585_power_on(struct device *dev) | |
{ | |
struct i2c_client *client = to_i2c_client(dev); | |
struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
struct imx585 *imx585 = to_imx585(sd); | |
int ret; | |
ret = regulator_bulk_enable(imx585_NUM_SUPPLIES, | |
imx585->supplies); | |
if (ret) { | |
dev_err(&client->dev, "%s: failed to enable regulators\n", | |
__func__); | |
return ret; | |
} | |
ret = clk_prepare_enable(imx585->xclk); | |
if (ret) { | |
dev_err(&client->dev, "%s: failed to enable clock\n", | |
__func__); | |
goto reg_off; | |
} | |
gpiod_set_value_cansleep(imx585->reset_gpio, 1); | |
usleep_range(imx585_XCLR_MIN_DELAY_US, | |
imx585_XCLR_MIN_DELAY_US + imx585_XCLR_DELAY_RANGE_US); | |
return 0; | |
reg_off: | |
regulator_bulk_disable(imx585_NUM_SUPPLIES, imx585->supplies); | |
return ret; | |
} | |
static int imx585_power_off(struct device *dev) | |
{ | |
struct i2c_client *client = to_i2c_client(dev); | |
struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
struct imx585 *imx585 = to_imx585(sd); | |
gpiod_set_value_cansleep(imx585->reset_gpio, 0); | |
regulator_bulk_disable(imx585_NUM_SUPPLIES, imx585->supplies); | |
clk_disable_unprepare(imx585->xclk); | |
/* Force reprogramming of the common registers when powered up again. */ | |
imx585->common_regs_written = false; | |
return 0; | |
} | |
static int __maybe_unused imx585_suspend(struct device *dev) | |
{ | |
struct i2c_client *client = to_i2c_client(dev); | |
struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
struct imx585 *imx585 = to_imx585(sd); | |
if (imx585->streaming) | |
imx585_stop_streaming(imx585); | |
return 0; | |
} | |
static int __maybe_unused imx585_resume(struct device *dev) | |
{ | |
struct i2c_client *client = to_i2c_client(dev); | |
struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
struct imx585 *imx585 = to_imx585(sd); | |
int ret; | |
if (imx585->streaming) { | |
ret = imx585_start_streaming(imx585); | |
if (ret) | |
goto error; | |
} | |
return 0; | |
error: | |
imx585_stop_streaming(imx585); | |
imx585->streaming = 0; | |
return ret; | |
} | |
static int imx585_get_regulators(struct imx585 *imx585) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
unsigned int i; | |
for (i = 0; i < imx585_NUM_SUPPLIES; i++) | |
imx585->supplies[i].supply = imx585_supply_name[i]; | |
return devm_regulator_bulk_get(&client->dev, | |
imx585_NUM_SUPPLIES, | |
imx585->supplies); | |
} | |
/* Verify chip ID */ | |
static int imx585_identify_module(struct imx585 *imx585, u32 expected_id) | |
{ | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
int ret; | |
u32 val; | |
ret = imx585_read_reg(imx585, IMX585_REG_CHIP_ID, | |
1, &val); | |
if (ret) { | |
dev_err(&client->dev, "failed to read chip id %x, with error %d\n", | |
expected_id, ret); | |
return ret; | |
} | |
dev_info(&client->dev, "Device found\n"); | |
return 0; | |
} | |
static int imx585_get_selection(struct v4l2_subdev *sd, | |
struct v4l2_subdev_state *sd_state, | |
struct v4l2_subdev_selection *sel) | |
{ | |
switch (sel->target) { | |
case V4L2_SEL_TGT_CROP: { | |
struct imx585 *imx585 = to_imx585(sd); | |
mutex_lock(&imx585->mutex); | |
sel->r = *__imx585_get_pad_crop(imx585, sd_state, sel->pad, | |
sel->which); | |
mutex_unlock(&imx585->mutex); | |
return 0; | |
} | |
case V4L2_SEL_TGT_NATIVE_SIZE: | |
sel->r.left = 0; | |
sel->r.top = 0; | |
sel->r.width = IMX585_NATIVE_WIDTH; | |
sel->r.height = IMX585_NATIVE_HEIGHT; | |
return 0; | |
case V4L2_SEL_TGT_CROP_DEFAULT: | |
case V4L2_SEL_TGT_CROP_BOUNDS: | |
sel->r.left = imx585_PIXEL_ARRAY_LEFT; | |
sel->r.top = imx585_PIXEL_ARRAY_TOP; | |
sel->r.width = imx585_PIXEL_ARRAY_WIDTH; | |
sel->r.height = imx585_PIXEL_ARRAY_HEIGHT; | |
return 0; | |
} | |
return -EINVAL; | |
} | |
static const struct v4l2_subdev_core_ops imx585_core_ops = { | |
.subscribe_event = v4l2_ctrl_subdev_subscribe_event, | |
.unsubscribe_event = v4l2_event_subdev_unsubscribe, | |
}; | |
static const struct v4l2_subdev_video_ops imx585_video_ops = { | |
.s_stream = imx585_set_stream, | |
}; | |
static const struct v4l2_subdev_pad_ops imx585_pad_ops = { | |
.enum_mbus_code = imx585_enum_mbus_code, | |
.get_fmt = imx585_get_pad_format, | |
.set_fmt = imx585_set_pad_format, | |
.get_selection = imx585_get_selection, | |
.enum_frame_size = imx585_enum_frame_size, | |
}; | |
static const struct v4l2_subdev_ops imx585_subdev_ops = { | |
.core = &imx585_core_ops, | |
.video = &imx585_video_ops, | |
.pad = &imx585_pad_ops, | |
}; | |
static const struct v4l2_subdev_internal_ops imx585_internal_ops = { | |
.open = imx585_open, | |
}; | |
/* Initialize control handlers */ | |
static int imx585_init_controls(struct imx585 *imx585) | |
{ | |
struct v4l2_ctrl_handler *ctrl_hdlr; | |
struct i2c_client *client = v4l2_get_subdevdata(&imx585->sd); | |
struct v4l2_fwnode_device_properties props; | |
int ret; | |
ctrl_hdlr = &imx585->ctrl_handler; | |
ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); | |
if (ret) | |
return ret; | |
mutex_init(&imx585->mutex); | |
ctrl_hdlr->lock = &imx585->mutex; | |
/* | |
* Create the controls here, but mode specific limits are setup | |
* in the imx585_set_framing_limits() call below. | |
*/ | |
/* By default, PIXEL_RATE is read only */ | |
imx585->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx585_ctrl_ops, | |
V4L2_CID_PIXEL_RATE, | |
0xffff, | |
0xffff, 1, | |
0xffff); | |
imx585->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx585_ctrl_ops, | |
V4L2_CID_VBLANK, 0, 0xfffff, 1, 0); | |
imx585->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx585_ctrl_ops, | |
V4L2_CID_HBLANK, 0, 0xffff, 1, 0); | |
imx585->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx585_ctrl_ops, | |
V4L2_CID_EXPOSURE, | |
IMX585_EXPOSURE_MIN, | |
IMX585_EXPOSURE_MAX, | |
IMX585_EXPOSURE_STEP, | |
IMX585_EXPOSURE_DEFAULT); | |
v4l2_ctrl_new_std(ctrl_hdlr, &imx585_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, | |
IMX585_ANA_GAIN_MIN, IMX585_ANA_GAIN_MAX, | |
IMX585_ANA_GAIN_STEP, IMX585_ANA_GAIN_DEFAULT); | |
imx585->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx585_ctrl_ops, | |
V4L2_CID_VFLIP, 0, 1, 1, 0); | |
if (imx585->vflip) | |
imx585->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; | |
if (ctrl_hdlr->error) { | |
ret = ctrl_hdlr->error; | |
dev_err(&client->dev, "%s control init failed (%d)\n", | |
__func__, ret); | |
goto error; | |
} | |
ret = v4l2_fwnode_device_parse(&client->dev, &props); | |
if (ret) | |
goto error; | |
ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx585_ctrl_ops, | |
&props); | |
if (ret) | |
goto error; | |
imx585->sd.ctrl_handler = ctrl_hdlr; | |
/* Setup exposure and frame/line length limits. */ | |
imx585_set_framing_limits(imx585); | |
return 0; | |
error: | |
v4l2_ctrl_handler_free(ctrl_hdlr); | |
mutex_destroy(&imx585->mutex); | |
return ret; | |
} | |
static void imx585_free_controls(struct imx585 *imx585) | |
{ | |
v4l2_ctrl_handler_free(imx585->sd.ctrl_handler); | |
mutex_destroy(&imx585->mutex); | |
} | |
static const struct imx585_compatible_data imx585_compatible = { | |
.chip_id = IMX585_CHIP_ID, | |
.extra_regs = { | |
.num_of_regs = 0, | |
.regs = NULL | |
} | |
}; | |
static const struct of_device_id imx585_dt_ids[] = { | |
{ .compatible = "sony,imx585", .data = &imx585_compatible }, | |
{ /* sentinel */ } | |
}; | |
static int imx585_probe(struct i2c_client *client) | |
{ | |
struct device *dev = &client->dev; | |
struct imx585 *imx585; | |
const struct of_device_id *match; | |
int ret; | |
imx585 = devm_kzalloc(&client->dev, sizeof(*imx585), GFP_KERNEL); | |
if (!imx585) | |
return -ENOMEM; | |
v4l2_i2c_subdev_init(&imx585->sd, client, &imx585_subdev_ops); | |
match = of_match_device(imx585_dt_ids, dev); | |
if (!match) | |
return -ENODEV; | |
imx585->compatible_data = | |
(const struct imx585_compatible_data *)match->data; | |
/* Get system clock (xclk) */ | |
imx585->xclk = devm_clk_get(dev, NULL); | |
if (IS_ERR(imx585->xclk)) { | |
dev_err(dev, "failed to get xclk\n"); | |
return PTR_ERR(imx585->xclk); | |
} | |
imx585->xclk_freq = clk_get_rate(imx585->xclk); | |
if (imx585->xclk_freq != IMX585_XCLK_FREQ) { | |
dev_err(dev, "xclk frequency not supported: %d Hz\n", | |
imx585->xclk_freq); | |
return -EINVAL; | |
} | |
ret = imx585_get_regulators(imx585); | |
if (ret) { | |
dev_err(dev, "failed to get regulators\n"); | |
return ret; | |
} | |
/* Request optional enable pin */ | |
imx585->reset_gpio = devm_gpiod_get_optional(dev, "reset", | |
GPIOD_OUT_HIGH); | |
/* | |
* The sensor must be powered for imx585_identify_module() | |
* to be able to read the CHIP_ID register | |
*/ | |
ret = imx585_power_on(dev); | |
if (ret) | |
return ret; | |
ret = imx585_identify_module(imx585, imx585->compatible_data->chip_id); | |
if (ret) | |
goto error_power_off; | |
/* Initialize default format */ | |
imx585_set_default_format(imx585); | |
/* Enable runtime PM and turn off the device */ | |
pm_runtime_set_active(dev); | |
pm_runtime_enable(dev); | |
pm_runtime_idle(dev); | |
/* This needs the pm runtime to be registered. */ | |
ret = imx585_init_controls(imx585); | |
if (ret) | |
goto error_power_off; | |
/* Initialize subdev */ | |
imx585->sd.internal_ops = &imx585_internal_ops; | |
imx585->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | | |
V4L2_SUBDEV_FL_HAS_EVENTS; | |
imx585->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; | |
/* Initialize source pads */ | |
imx585->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; | |
imx585->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; | |
ret = media_entity_pads_init(&imx585->sd.entity, NUM_PADS, imx585->pad); | |
if (ret) { | |
dev_err(dev, "failed to init entity pads: %d\n", ret); | |
goto error_handler_free; | |
} | |
ret = v4l2_async_register_subdev_sensor(&imx585->sd); | |
if (ret < 0) { | |
dev_err(dev, "failed to register sensor sub-device: %d\n", ret); | |
goto error_media_entity; | |
} | |
return 0; | |
error_media_entity: | |
media_entity_cleanup(&imx585->sd.entity); | |
error_handler_free: | |
imx585_free_controls(imx585); | |
error_power_off: | |
pm_runtime_disable(&client->dev); | |
pm_runtime_set_suspended(&client->dev); | |
imx585_power_off(&client->dev); | |
return ret; | |
} | |
static void imx585_remove(struct i2c_client *client) | |
{ | |
struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
struct imx585 *imx585 = to_imx585(sd); | |
v4l2_async_unregister_subdev(sd); | |
media_entity_cleanup(&sd->entity); | |
imx585_free_controls(imx585); | |
pm_runtime_disable(&client->dev); | |
if (!pm_runtime_status_suspended(&client->dev)) | |
imx585_power_off(&client->dev); | |
pm_runtime_set_suspended(&client->dev); | |
} | |
MODULE_DEVICE_TABLE(of, imx585_dt_ids); | |
static const struct dev_pm_ops imx585_pm_ops = { | |
SET_SYSTEM_SLEEP_PM_OPS(imx585_suspend, imx585_resume) | |
SET_RUNTIME_PM_OPS(imx585_power_off, imx585_power_on, NULL) | |
}; | |
static struct i2c_driver imx585_i2c_driver = { | |
.driver = { | |
.name = "imx585", | |
.of_match_table = imx585_dt_ids, | |
.pm = &imx585_pm_ops, | |
}, | |
.probe_new = imx585_probe, | |
.remove = imx585_remove, | |
}; | |
module_i2c_driver(imx585_i2c_driver); | |
MODULE_AUTHOR("Will Whang <[email protected]>"); | |
MODULE_DESCRIPTION("Sony imx585 sensor driver"); | |
MODULE_LICENSE("GPL v2"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment