Tool/software: Linux
Hi,
I'm having trouble interfacing a custom camera board I've been working on for the AM572x platform. The camera board is build around a OV5640 sensor and we are using 8-bit DVP interface for data output.
We are running linux kernel 4.4.46 on the module and I've been working on a v4l2 driver in order to interface the camera to the board. I've successfully managed to get the camera driver to detect and bind to the VIP3A port as well as the camera itself.
The trouble I'm experiencing is getting an sensible image output from the camera as for what is expected. I'm outputting a standard color bar test image from the camera in YUV422 format (YUYV) , and I'm able to capture complete frames using the qv4l2 tool. However my output seen below is far from what is expected, and are independent on the bit order (reversing the output order from the camera does not affect the image itself).
The VSYNC, HSYNC and PCLK signals is working as they should (as far as I'm aware), a sample of the using a logic analyser can be seen below.
At this point I've been stuck for some time now, being unable to get any proper output from the camera. I've crosschecked the camera setting with numerus other sources and they check out, also there is data present on all data lines. My thought is that I must have missed something in either the driver or how to properly use the the VIP port for this purpose as I'm very new to this field. The driver I'm running is a brew of a old ov5640 v4l2 driver that I've tried to port for the AM572x using the existing TI mt9t11x driver.
/*
* OmniVision OV5640 sensor driver
*
* Based on the OV5640 driver written by Sergio Aguirre <sergio.a.aguirre@gmail.com>
* and the TI mt9t11x driver by Benoit Parrot <bparrot@ti.com>
*
* Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/media.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/videodev2.h>
#include <media/media-entity.h>
#include <media/ov5640.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-image-sizes.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-of.h>
#include <media/v4l2-subdev.h>
/* OV5640 has only one fixed colorspace per pixelcode */
struct ov5640_datafmt {
u32 code;
enum v4l2_colorspace colorspace;
};
struct ov5640_timing_cfg {
u16 x_addr_start;
u16 y_addr_start;
u16 x_addr_end;
u16 y_addr_end;
u16 h_output_size;
u16 v_output_size;
u16 h_total_size;
u16 v_total_size;
u16 isp_h_offset;
u16 isp_v_offset;
u8 h_odd_ss_inc;
u8 h_even_ss_inc;
u8 v_odd_ss_inc;
u8 v_even_ss_inc;
};
struct ov5640_clk_cfg {
u8 sc_pll_prediv;
u8 sc_pll_rdiv;
u8 sc_pll_mult;
u8 sysclk_div;
u8 mipi_div;
};
enum ov5640_size {
OV5640_SIZE_QVGA,
OV5640_SIZE_VGA,
OV5640_SIZE_720P,
OV5640_SIZE_1080P,
OV5640_SIZE_5MP,
OV5640_SIZE_LAST,
};
static const struct v4l2_frmsize_discrete ov5640_frmsizes[OV5640_SIZE_LAST] = {
{ 320, 240 },
{ 640, 480 },
{ 1280, 720 },
{ 1920, 1080 },
{ 2592, 1944 },
};
/* Find a frame size in an array */
static int ov5640_find_framesize(u32 width, u32 height)
{
int i;
for (i = 0; i < OV5640_SIZE_LAST; i++) {
if ((ov5640_frmsizes[i].width >= width) &&
(ov5640_frmsizes[i].height >= height))
break;
}
/* If not found, select biggest */
if (i >= OV5640_SIZE_LAST)
i = OV5640_SIZE_LAST - 1;
return i;
}
struct ov5640 {
unsigned short id;
struct v4l2_subdev subdev;
struct media_pad pad;
struct v4l2_mbus_framefmt format;
struct v4l2_ctrl_handler ctrl_handler;
const struct ov5640_platform_data *pdata;
struct v4l2_ctrl *pixel_rate;
struct v4l2_ctrl *bitorder;
struct v4l2_ctrl *vsync;
struct v4l2_ctrl *href;
struct v4l2_ctrl *pclkctl;
struct v4l2_ctrl *gpclkvsync;
struct v4l2_ctrl *gpclkhref;
/* HW control */
struct clk *xvclk;
struct regulator *avdd;
struct regulator *dovdd;
/* System Clock config */
struct ov5640_clk_cfg clk_cfg;
/* clocks, no need really? */
int mclk, pclk;
};
static inline struct ov5640 *to_ov5640(struct v4l2_subdev *sd)
{
return container_of(sd, struct ov5640, subdev);
}
/**
* struct ov5640_reg - ov5640 register format
* @reg: 16-bit offset to register
* @val: 8/16/32-bit register value
* @length: length of the register
*
* Define a structure for OV5640 register initialization values
*/
struct ov5640_reg {
u16 reg;
u8 val;
};
/* TODO: Divide this properly */
static const struct ov5640_reg configscript_common1[] = {
{ 0x3008, 0x42 }, // SOFT POWERDOWN
{ 0x3103, 0x03 }, // SCCB CLK SOURCE
{ 0x3017, 0x7f }, // I/O
{ 0x3018, 0xf3 }, // I/O
{ 0x302c, 0x02 }, // I/O drive capability: x3
{ 0x3630, 0x2e }, // Not in DS
{ 0x3632, 0xe2 }, // Not in DS
{ 0x3633, 0x23 }, // Not in DS
{ 0x3634, 0x44 }, // Not in DS
{ 0x3621, 0xe0 }, // Not in DS
{ 0x3704, 0xa0 }, // Not in DS
{ 0x3703, 0x5a }, // Not in DS
{ 0x3715, 0x78 }, // Not in DS
{ 0x3717, 0x01 }, // Not in DS
{ 0x370b, 0x60 }, // Not in DS
{ 0x3705, 0x1a }, // Not in DS
{ 0x3905, 0x02 }, // Not in DS
{ 0x3906, 0x10 }, // Not in DS
{ 0x3901, 0x0a }, // Not in DS
{ 0x3731, 0x12 }, // Not in DS
{ 0x3600, 0x04 }, // VCM Debug mode
{ 0x3601, 0x22 }, // VCM Debug mode
{ 0x471c, 0x50 }, // Not in DS
{ 0x3002, 0x1c }, // System Reset 2: Reset all blocks
{ 0x3006, 0xc3 }, // Clock Enable: PSRAM, FMT, format MUX, avg. CLK
{ 0x300e, 0x59 }, // MIPI Control 00: DVP mode, POWER DOWN PHY RX/TX
{ 0x302e, 0x08 }, //Not in DS
{ 0x3612, 0x4b },
{ 0x3618, 0x04 },
{ 0x3031, 0x80}, // Bypass internal 1.5V LDO
{ 0x3034, 0x11 }, // PLL CP CTL: 1, MIPI bit mode: Debug
{ 0x3035, 0x11 }, // No system clock dividing
{ 0x3036, 0x54 }, // PLL multiplication: *84
{ 0x3037, 0x13 }, // PLL divider: PLL root / 2, pre-div / 3
{ 0x3039, 0x80 }, // PLL bypass. With bypass: PCLK: 1MHZ (?).
{ 0x3708, 0x21 }, // Not in DS
{ 0x3709, 0x12 }, // Not in DS
{ 0x370c, 0x00 }, // Not in DS
{ 0x4740, 0x21 }, // POLARITY CTRL00: PCLK, VSYNC: Active High. HREF active LOW
//{ 0x471B, 0x21 }, // HSYNC MODE enabled.
{ 0x471D, 0x21 }, // VSYNC MODE: pos edge, trig. on EOF.
//{ 0x3821, 0x01 }, // Horizontal Binning! (?)
// Timing HTS and VTS
{ 0x380c, 0x07 },
{ 0x380d, 0x68 },
{ 0x380e, 0x03 }, //03
{ 0x380f, 0xd8 }, //d8
{ 0x3000, 0x00 }, // SYSTEM RESET
{ 0x4300, 0x32 }, // FORMAT CTRL: UYVU
{ 0x501f, 0x00 }, // FORMAT MUX: ISP YUV422
{ 0x4713, 0x03 }, // JPEG mode 3
{ 0x460c, 0x20 }, // DVP PCLK DIVIDER BY 0x3824
{ 0x3824, 0x01 }, // DVP PCLK / 1
{ 0x3003, 0x03 }, // RESET: MIPI + DVP
{ 0x3003, 0x02 }, // RESET: MIPI
{ 0x4741, 0x05 },
{ 0x503d, 0x80 },
//{ 0x4745, 0x01 }, // Data order reverse?
{ 0x3008, 0x02 }, // SOFT POWER ON
};
/* TODO: Divide this properly */
static const struct ov5640_reg configscript_common2[] = {
{ 0x3a02, 0x01 }, // AEC
{ 0x3a03, 0xec }, // AEC
{ 0x3a08, 0x01 }, // AEC
{ 0x3a09, 0x27 }, // AEC
{ 0x3a0a, 0x00 }, // AEC
{ 0x3a0b, 0xf6 }, // AEC
{ 0x3a0e, 0x06 }, // AEC
{ 0x3a0d, 0x08 }, // AEC
{ 0x3a14, 0x01 }, // AEC
{ 0x3a15, 0xec }, // AEC
{ 0x4001, 0x02 }, // BLC
{ 0x4004, 0x06 }, // BLC
{ 0x460b, 0x37 }, // Debug mode
{ 0x4750, 0x00 }, // Not in DS
{ 0x4751, 0x00 }, // Not in DS
{ 0x4800, 0x04 }, // MIPI CTRL: Default
{ 0x5a00, 0x08 }, // Not in DS
{ 0x5a21, 0x00 }, // Not in DS
{ 0x5a24, 0x00 }, // Not in DS
{ 0x5000, 0x27 }, // ISP CTRL 00: RAW GMA Enable, BPC, WPC, CI enable
{ 0x5001, 0xa3 }, // ISP CTRL 01: SDE, UV avg, CM, AWB enable
{ 0x3820, 0x40 }, // ISP vflip
{ 0x3821, 0x06 }, // ISP + Sensor mirror
{ 0x3824, 0x01 }, // DVP PCLK divider: / 1
{ 0x5481, 0x08 }, // GAMMA
{ 0x5482, 0x14 }, // GAMMA
{ 0x5483, 0x28 }, // GAMMA
{ 0x5484, 0x51 }, // GAMMA
{ 0x5485, 0x65 }, // GAMMA
{ 0x5486, 0x71 }, // GAMMA
{ 0x5487, 0x7d }, // GAMMA
{ 0x5488, 0x87 }, // GAMMA
{ 0x5489, 0x91 }, // GAMMA
{ 0x548a, 0x9a }, // GAMMA
{ 0x548b, 0xaa }, // GAMMA
{ 0x548c, 0xb8 }, // GAMMA
{ 0x548d, 0xcd }, // GAMMA
{ 0x548e, 0xdd }, // GAMMA
{ 0x548f, 0xea }, // GAMMA
{ 0x5490, 0x1d }, // GAMMA
{ 0x5381, 0x20 }, // CM1 (Color Matrix) for Y
{ 0x5382, 0x64 }, // CM2 (Color Matrix) for Y
{ 0x5383, 0x08 }, // CM3 (Color Matrix) for Y
{ 0x5384, 0x20 }, // CM4 (Color Matrix) for U
{ 0x5385, 0x80 }, // CM5 (Color Matrix) for U
{ 0x5386, 0xa0 }, // CM6 (Color Matrix) for U
{ 0x5387, 0xa2 }, // CM7 (Color Matrix) for V
{ 0x5388, 0xa0 }, // CM8 (Color Matrix) for V
{ 0x5389, 0x02 }, // CM9 (Color Matrix) for V
{ 0x538a, 0x01 }, // CM9 sign
{ 0x538b, 0x98 }, // CM1-8 sign
{ 0x5300, 0x08 }, // CIP (Color Interpolation) TH1
{ 0x5301, 0x30 }, // CIP (Color Interpolation) TH2
{ 0x5302, 0x10 }, // CIP (Color Interpolation) SHARPEN OFFSET1
{ 0x5303, 0x00 }, // CIP (Color Interpolation) SHARPEN OFFSET2
{ 0x5304, 0x08 }, // CIP (Color Interpolation) DNS TH1
{ 0x5305, 0x30 }, // CIP (Color Interpolation) DNS TH2
{ 0x5306, 0x08 }, // CIP (Color Interpolation) DNS OFFSET1
{ 0x5307, 0x16 }, // CIP (Color Interpolation) DNS OFFSET2
{ 0x5580, 0x00 }, // SDE CTRL0
{ 0x5587, 0x00 }, // SDE CTRL7
{ 0x5588, 0x00 }, // SDE CTRL8
{ 0x5583, 0x40 }, // SDE CTRL3
{ 0x5584, 0x10 }, // SDE CTRL4
{ 0x5589, 0x10 }, // SDE CTRL9
{ 0x558a, 0x00 }, // SDE CTRL10
{ 0x558b, 0xf8 }, // SDE CTRL11
{ 0x3a0f, 0x36 }, // AEC CTRL0F
{ 0x3a10, 0x2e }, // AEC CTRL10
{ 0x3a1b, 0x38 }, // AEC CTRL1B
{ 0x3a1e, 0x2c }, // AEC CTRL1E
{ 0x3a11, 0x70 }, // AEC CTRL11
{ 0x3a1f, 0x18 }, // AEC CTRL1F
{ 0x3a18, 0x00 }, // AEC GAIN CEILING
{ 0x3a19, 0xf8 }, // AEC GAIN CEILING
{ 0x3003, 0x03 }, // RESET: MIPI + DVP
{ 0x3003, 0x02 }, // RESET: MIPI
};
static const struct ov5640_timing_cfg timing_cfg[OV5640_SIZE_LAST] = {
[OV5640_SIZE_QVGA] = {
.x_addr_start = 0,
.y_addr_start = 0,
.x_addr_end = 2623,
.y_addr_end = 1951,
.h_output_size = 320,
.v_output_size = 240,
.h_total_size = 2844,
.v_total_size = 1968,
.isp_h_offset = 16,
.isp_v_offset = 6,
.h_odd_ss_inc = 1,
.h_even_ss_inc = 1,
.v_odd_ss_inc = 1,
.v_even_ss_inc = 1,
},
[OV5640_SIZE_VGA] = {
.x_addr_start = 0,
.y_addr_start = 0,
.x_addr_end = 2623,
.y_addr_end = 1951,
.h_output_size = 640,
.v_output_size = 480,
.h_total_size = 3200,
.v_total_size = 2000,
.isp_h_offset = 16,
.isp_v_offset = 6,
.h_odd_ss_inc = 3,
.h_even_ss_inc = 1,
.v_odd_ss_inc = 3,
.v_even_ss_inc = 1,
},
[OV5640_SIZE_720P] = {
.x_addr_start = 336,
.y_addr_start = 434,
.x_addr_end = 2287,
.y_addr_end = 1522,
.h_output_size = 1280,
.v_output_size = 720,
.h_total_size = 2500,
.v_total_size = 1120,
.isp_h_offset = 16,
.isp_v_offset = 4,
.h_odd_ss_inc = 1,
.h_even_ss_inc = 1,
.v_odd_ss_inc = 1,
.v_even_ss_inc = 1,
},
[OV5640_SIZE_1080P] = {
.x_addr_start = 336,
.y_addr_start = 434,
.x_addr_end = 2287,
.y_addr_end = 1522,
.h_output_size = 1920,
.v_output_size = 1080,
.h_total_size = 2500,
.v_total_size = 1120,
.isp_h_offset = 16,
.isp_v_offset = 4,
.h_odd_ss_inc = 1,
.h_even_ss_inc = 1,
.v_odd_ss_inc = 1,
.v_even_ss_inc = 1,
},
[OV5640_SIZE_5MP] = {
.x_addr_start = 0,
.y_addr_start = 0,
.x_addr_end = 2623,
.y_addr_end = 1951,
.h_output_size = 2592,
.v_output_size = 1944,
.h_total_size = 2844,
.v_total_size = 1968,
.isp_h_offset = 16,
.isp_v_offset = 6,
.h_odd_ss_inc = 1,
.h_even_ss_inc = 1,
.v_odd_ss_inc = 1,
.v_even_ss_inc = 1,
},
};
/**
* ov5640_reg_read - Read a value from a register in an ov5640 sensor device
* @client: i2c driver client structure
* @reg: register address / offset
* @val: stores the value that gets read
*
* Read a value from a register in an ov5640 sensor device.
* The value is returned in 'val'.
* Returns zero if successful, or non-zero otherwise.
*/
static int ov5640_reg_read(struct i2c_client *client, u16 reg, u8 *val)
{
int ret;
u8 data[2] = {0};
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = data,
};
data[0] = (u8)(reg >> 8);
data[1] = (u8)(reg & 0xff);
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
goto err;
msg.flags = I2C_M_RD;
msg.len = 1;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
goto err;
*val = data[0];
return 0;
err:
dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg);
return ret;
}
/**
* Write a value to a register in ov5640 sensor device.
* @client: i2c driver client structure.
* @reg: Address of the register to read value from.
* @val: Value to be written to a specific register.
* Returns zero if successful, or non-zero otherwise.
*/
static int ov5640_reg_write(struct i2c_client *client, u16 reg, u8 val)
{
int ret;
unsigned char data[3] = { (u8)(reg >> 8), (u8)(reg & 0xff), val };
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 3,
.buf = data,
};
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0) {
int i = 0;
do{
ret = i2c_transfer(client->adapter, &msg, 1);
if(ret < 0)
i++;
else
break;
}while(i < 5);
// Retry writing 5 times */
if (ret < 0) {
dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg);
return ret;
}
}
return 0;
}
/* * Initialize a list of ov5640 registers. */
/* * The list of registers is terminated by the pair of values */
/* * @client: i2c driver client structure. */
/* * @reglist[]: List of address of the registers to write data. */
/* * Returns zero if successful, or non-zero otherwise. */
static int ov5640_reg_writes(struct i2c_client *client, const struct ov5640_reg reglist[], int size)
{
int err = 0, i;
for (i = 0; i < size; i++) {
err = ov5640_reg_write(client, reglist[i].reg,
reglist[i].val);
if (err)
return err;
}
return 0;
}
static int ov5640_reg_set(struct i2c_client *client, u16 reg, u8 val)
{
int ret;
u8 tmpval = 0;
ret = ov5640_reg_read(client, reg, &tmpval);
if (ret)
return ret;
return ov5640_reg_write(client, reg, tmpval | val);
}
static int ov5640_reg_clr(struct i2c_client *client, u16 reg, u8 val)
{
int ret;
u8 tmpval = 0;
ret = ov5640_reg_read(client, reg, &tmpval);
if (ret)
return ret;
return ov5640_reg_write(client, reg, tmpval & ~val);
}
/* Used for MIPI
static unsigned long ov5640_get_pclk(struct v4l2_subdev *sd)
{
struct ov5640 *ov5640 = to_ov5640(sd);
unsigned long xvclk, vco, mipi_pclk;
xvclk = clk_get_rate(ov5640->xvclk);
vco = (xvclk / ov5640->clk_cfg.sc_pll_prediv) *
ov5640->clk_cfg.sc_pll_mult;
mipi_pclk = vco /
ov5640->clk_cfg.sysclk_div /
ov5640->clk_cfg.mipi_div;
return mipi_pclk;
}
*/
static int ov5640_config_timing(struct v4l2_subdev *sd)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct ov5640 *ov5640 = to_ov5640(sd);
int ret, i;
i = ov5640_find_framesize(ov5640->format.width, ov5640->format.height);
dev_info(&client->dev,"Setting format W/H: %d/%d",ov5640->format.width, ov5640->format.height);
dev_info(&client->dev,"Setting format index: %d\n",i);
ret = ov5640_reg_write(client,
0x3800,
(timing_cfg[i].x_addr_start & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3801,
timing_cfg[i].x_addr_start & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3802,
(timing_cfg[i].y_addr_start & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3803,
timing_cfg[i].y_addr_start & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3804,
(timing_cfg[i].x_addr_end & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3805,
timing_cfg[i].x_addr_end & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3806,
(timing_cfg[i].y_addr_end & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3807,
timing_cfg[i].y_addr_end & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3808,
(timing_cfg[i].h_output_size & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3809,
timing_cfg[i].h_output_size & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x380A,
(timing_cfg[i].v_output_size & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x380B,
timing_cfg[i].v_output_size & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x380C,
(timing_cfg[i].h_total_size & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x380D,
timing_cfg[i].h_total_size & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x380E,
(timing_cfg[i].v_total_size & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x380F,
timing_cfg[i].v_total_size & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3810,
(timing_cfg[i].isp_h_offset & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3811,
timing_cfg[i].isp_h_offset & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3812,
(timing_cfg[i].isp_v_offset & 0xFF00) >> 8);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3813,
timing_cfg[i].isp_v_offset & 0xFF);
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3814,
((timing_cfg[i].h_odd_ss_inc & 0xF) << 4) |
(timing_cfg[i].h_even_ss_inc & 0xF));
if (ret)
return ret;
ret = ov5640_reg_write(client,
0x3815,
((timing_cfg[i].v_odd_ss_inc & 0xF) << 4) |
(timing_cfg[i].v_even_ss_inc & 0xF));
return ret;
}
static struct v4l2_mbus_framefmt *
__ov5640_get_pad_format(struct ov5640 *ov5640, struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg, enum v4l2_subdev_format_whence which)
{
switch (which) {
case V4L2_SUBDEV_FORMAT_TRY:
return v4l2_subdev_get_try_format(sd, cfg, 0);
case V4L2_SUBDEV_FORMAT_ACTIVE:
return &ov5640->format;
default:
return NULL;
}
}
/* -----------------------------------------------------------------------------
* V4L2 subdev internal operations
*/
static int ov5640_s_power(struct v4l2_subdev *sd, int on)
{
struct ov5640 *ov5640 = to_ov5640(sd);
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct device *dev = &client->dev;
if (on) {
int ret;
if (ov5640->pdata->pre_poweron) {
ret = ov5640->pdata->pre_poweron(sd);
if (ret) {
dev_err(dev,
"Error in pre_poweron (%d)\n", ret);
return ret;
}
}
if (ov5640->dovdd) {
//ret = regulator_enable(ov5640->dovdd);
if (ret) {
dev_err(dev,
"Error in enabling DOVDD (%d)\n", ret);
if (ov5640->pdata->post_poweroff)
ov5640->pdata->post_poweroff(sd);
return ret;
}
}
if (ov5640->avdd) {
//ret = regulator_enable(ov5640->avdd);
if (ret) {
dev_err(dev,
"Error in enabling AVDD (%d)\n", ret);
if (ov5640->dovdd)
//regulator_disable(ov5640->dovdd);
if (ov5640->pdata->post_poweroff)
ov5640->pdata->post_poweroff(sd);
return ret;
}
usleep_range(5000, 5000);
}
/*
//ret = clk_enable(ov5640->xvclk);
if (ret) {
dev_err(dev, "Error in enabling XVCLK (%d)\n", ret);
if (ov5640->avdd)
//regulator_disable(ov5640->avdd);
if (ov5640->dovdd)
//regulator_disable(ov5640->dovdd);
if (ov5640->pdata->post_poweroff)
ov5640->pdata->post_poweroff(sd);
return ret;
}
*/
if (gpio_is_valid(ov5640->pdata->gpio_pwdn)) {
gpio_set_value(ov5640->pdata->gpio_pwdn,
ov5640->pdata->is_gpio_pwdn_acthi ?
1 : 0);
}
usleep_range(2000, 2000);
} else {
if (gpio_is_valid(ov5640->pdata->gpio_pwdn)) {
gpio_set_value(ov5640->pdata->gpio_pwdn,
ov5640->pdata->is_gpio_pwdn_acthi ?
0 : 1);
}
/*
clk_disable(ov5640->xvclk);
if (ov5640->avdd)
//regulator_disable(ov5640->avdd);
if (ov5640->dovdd)
//regulator_disable(ov5640->dovdd);
if (ov5640->pdata->post_poweroff)
ov5640->pdata->post_poweroff(sd);
*/
}
return 0;
}
static struct v4l2_subdev_core_ops ov5640_subdev_core_ops = {
.s_power = ov5640_s_power,
};
static int ov5640_g_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *format)
{
struct ov5640 *ov5640 = to_ov5640(sd);
format->format = *__ov5640_get_pad_format(ov5640, sd, cfg,
format->which);
return 0;
}
static int ov5640_s_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *format)
{
struct ov5640 *ov5640 = to_ov5640(sd);
struct v4l2_mbus_framefmt *__format = &format->format;
__format = __ov5640_get_pad_format(ov5640, sd, cfg,
format->which);
//ov5640->pixel_rate->cur.val = ov5640_get_pclk(sd) / 16;
return 0;
}
static int ov5640_enum_fmt(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index >= 2)
return -EINVAL;
switch (code->index) {
case 0:
code->code = MEDIA_BUS_FMT_UYVY8_2X8;
break;
case 1:
code->code = MEDIA_BUS_FMT_YUYV8_2X8;
break;
}
return 0;
}
static int ov5640_enum_framesizes(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_frame_size_enum *fse)
{
if ((fse->index >= OV5640_SIZE_LAST) ||
(fse->code != MEDIA_BUS_FMT_UYVY8_2X8 &&
fse->code != MEDIA_BUS_FMT_YUYV8_2X8))
return -EINVAL;
fse->min_width = ov5640_frmsizes[fse->index].width;
fse->max_width = fse->min_width;
fse->min_height = ov5640_frmsizes[fse->index].height;
fse->max_height = fse->min_height;
return 0;
}
static int ov5640_s_stream(struct v4l2_subdev *sd, int enable)
{
struct ov5640 *ov5640 = to_ov5640(sd);
struct i2c_client *client = v4l2_get_subdevdata(sd);
int ret = 0;
if (enable) {
ov5640_s_power(&ov5640->subdev, 1);
u8 fmtreg = 0, fmtmuxreg = 0;
int i;
switch ((u32)ov5640->format.code) {
case MEDIA_BUS_FMT_UYVY8_2X8:
fmtreg = 0x32;
fmtmuxreg = 0;
break;
case MEDIA_BUS_FMT_YUYV8_2X8:
fmtreg = 0x30;
fmtmuxreg = 0;
break;
default:
/* This shouldn't happen */
dev_info(&client->dev,"...But it did... code: %d\n",(u32)ov5640->format.code);
ret = -EINVAL;
return ret;
}
ret = ov5640_reg_write(client, 0x4300, fmtreg);
if (ret)
return ret;
ret = ov5640_reg_write(client, 0x501F, fmtmuxreg);
if (ret)
return ret;
ret = ov5640_config_timing(sd);
if (ret)
return ret;
i = ov5640_find_framesize(ov5640->format.width, ov5640->format.height);
char bitorder;
ret = ov5640_reg_read(client, 0x4745, &bitorder);
dev_info(&client->dev,"Bitorder: %x",bitorder);
/*
if ((i == OV5640_SIZE_QVGA) ||
(i == OV5640_SIZE_VGA) ||
(i == OV5640_SIZE_720P)) {
ret = ov5640_reg_write(client, 0x3108,
(i == OV5640_SIZE_720P) ? 0x1 : 0);
if (ret)
return ret;
ret = ov5640_reg_set(client, 0x5001, 0x87); //prev 0x20
} else {
ret = ov5640_reg_clr(client, 0x5001, 0x87); //prev 0x20
if (ret)
return ret;
ret = ov5640_reg_write(client, 0x3108, 0x2);
}
*/
ret = ov5640_reg_clr(client, 0x3008, 0x40); // turn power on soft
dev_info(&client->dev,"SW PWDUP (new) RET %d\n",ret);
if (ret)
goto out;
} else {
u8 tmpreg = 0;
ret = ov5640_reg_read(client, 0x3008, &tmpreg);
//dev_info(&client->dev,"SW PWDUP %d\n",0);
if (ret)
goto out;
ret = ov5640_reg_write(client, 0x3008, tmpreg | 0x40); // turn power off soft
dev_info(&client->dev,"SW PWDN %d\n",1);
if (ret)
goto out;
ov5640_s_power(&ov5640->subdev, 0);
}
out:
return ret;
}
static int get_config(struct v4l2_subdev *sd,struct v4l2_mbus_config *cfg) {
struct ov5640 *ov5640 = to_ov5640(sd);
struct i2c_client *client = v4l2_get_subdevdata(sd);
dev_info(&client->dev,"GET_MBUS_CONFIG FLAGS: %d\n",cfg->flags);
//Do nothing
return 0;
}
static struct v4l2_subdev_video_ops ov5640_subdev_video_ops = {
.s_stream = ov5640_s_stream,
.g_mbus_config = get_config,
};
static struct v4l2_subdev_pad_ops ov5640_subdev_pad_ops = {
.enum_mbus_code = ov5640_enum_fmt,
.enum_frame_size = ov5640_enum_framesizes,
.get_fmt = ov5640_g_fmt,
.set_fmt = ov5640_s_fmt,
};
static int ov5640_g_skip_frames(struct v4l2_subdev *sd, u32 *frames)
{
/* Quantity of initial bad frames to skip. Revisit. */
*frames = 3;
return 0;
}
static struct v4l2_subdev_sensor_ops ov5640_subdev_sensor_ops = {
.g_skip_frames = ov5640_g_skip_frames,
};
static struct v4l2_subdev_ops ov5640_subdev_ops = {
.core = &ov5640_subdev_core_ops,
.video = &ov5640_subdev_video_ops,
.pad = &ov5640_subdev_pad_ops,
.sensor = &ov5640_subdev_sensor_ops,
};
static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct ov5640 *priv = container_of(ctrl->handler, struct ov5640, ctrl_handler);;
struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev);
int ret = -EINVAL;
dev_dbg(&client->dev, "%s: ctrl_id:0x%x (%s), value: %d\n",
__func__, ctrl->id, ctrl->name, ctrl->val);
char value1;
char value2;
switch (ctrl->id) {
case V4L2_CID_AUTO_WHITE_BALANCE : // Change bitorder
ret = ov5640_reg_read(client, 0x4745, &value1);
value2 = value1;
value2 ^= 0x01;
ret = ov5640_reg_write(client, 0x4745, value2);
dev_info(&client->dev,"Bitorder was %x, changed to %x",value1,value2);
ret = 0;
break;
case V4L2_CID_HFLIP: // Change PCLK polarity
ret = ov5640_reg_read(client, 0x4740, &value1);
value2 = value1;
value2 ^= 0x20;
ret = ov5640_reg_write(client, 0x4740, value2);
dev_info(&client->dev,"PCLK polarity was %x, changed to %x",value1,value2);
ret = 0;
break;
case V4L2_CID_AUDIO_LOUDNESS : // Change VSYNC polarity
ret = ov5640_reg_read(client, 0x4740, &value1);
value2 = value1;
value2 ^= 0x01;
ret = ov5640_reg_write(client, 0x4740, value2);
dev_info(&client->dev,"VSYNC polarity was %x, changed to %x",value1,value2);
ret = 0;
break;
case V4L2_CID_VFLIP: // Change HREF polarity
ret = ov5640_reg_read(client, 0x4740, &value1);
value2 = value1;
value2 ^= 0x02;
ret = ov5640_reg_write(client, 0x4740, value2);
dev_info(&client->dev,"HREF polarity was %x, changed to %x",value1,value2);
ret = 0;
break;
case V4L2_CID_AUDIO_MUTE : // Change GATE PCLK
ret = ov5640_reg_read(client, 0x4740, &value1);
value2 = value1;
value2 ^= 0x04;
ret = ov5640_reg_write(client, 0x4740, value2);
dev_info(&client->dev,"gate PCLK under HREF was %x, changed to %x",value1,value2);
ret = 0;
break;
case V4L2_CID_HUE_AUTO : // Change GATE PCLK
ret = ov5640_reg_read(client, 0x4740, &value1);
value2 = value1;
value2 ^= 0x08;
ret = ov5640_reg_write(client, 0x4740, value2);
dev_info(&client->dev,"gate PCLK under VSYNC was %x, changed to %x",value1,value2);
ret = 0;
break;
}
return ret;
};
static const struct v4l2_ctrl_ops ov5640_ctrl_ops = {
.s_ctrl = ov5640_s_ctrl,
};
static int ov5640_registered(struct v4l2_subdev *subdev)
{
const struct v4l2_ctrl_ops *ops = &ov5640_ctrl_ops;
struct i2c_client *client = v4l2_get_subdevdata(subdev);
struct ov5640 *ov5640 = to_ov5640(subdev);
int ret = 0;
u8 revision = 0;
u8 msb;
u8 lsb;
ret = ov5640_s_power(subdev, 1);
if (ret < 0) {
dev_err(&client->dev, "OV5640 power up failed\n");
return ret;
}
ret = ov5640_reg_read(client, 0x302A, &revision);
if (ret) {
dev_err(&client->dev, "Failure to detect OV5640 chip\n");
goto out;
}
revision &= 0xF;
dev_info(&client->dev, "Detected a OV5640 chip, revision %x\n",
revision);
/* SW Reset */
ret = ov5640_reg_set(client, 0x3008, 0x80);
if (ret)
goto out;
msleep(2);
ret = ov5640_reg_clr(client, 0x3008, 0x80);
if (ret)
goto out;
/* SW Powerdown */
ret = ov5640_reg_set(client, 0x3008, 0x40);
if (ret)
goto out;
/* Check chip id */
ret = ov5640_reg_read(client, 0x300A, &msb);
if (!ret)
ret = ov5640_reg_read(client, 0x300B, &lsb);
if (!ret) {
ov5640->id = (msb << 8) | lsb;
if (ov5640->id == 0x5640)
dev_info(&client->dev, "Sensor ID: %04X\n", ov5640->id);
else
dev_err(&client->dev, "Sensor detection failed (%04X, %d)\n",
ov5640->id, ret);
}
ret = ov5640_reg_writes(client, configscript_common1,
ARRAY_SIZE(configscript_common1));
if (ret)
goto out;
ret = ov5640_reg_writes(client, configscript_common2,
ARRAY_SIZE(configscript_common2));
if (ret)
goto out;
/* Init controls */
ret = v4l2_ctrl_handler_init(&ov5640->ctrl_handler, 7);
if (ret)
goto out;
ov5640->pixel_rate = v4l2_ctrl_new_std(
&ov5640->ctrl_handler, NULL,
V4L2_CID_PIXEL_RATE,
0, 0, 1, 0);
dev_info(&client->dev,"Init extra controls");
/* Reuse comon controls for other purposes */
ov5640->bitorder = v4l2_ctrl_new_std(&ov5640->ctrl_handler, ops,V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 0);
ov5640->pclkctl = v4l2_ctrl_new_std(&ov5640->ctrl_handler, ops,V4L2_CID_HFLIP , 0, 1, 1, 0);
ov5640->vsync = v4l2_ctrl_new_std(&ov5640->ctrl_handler, ops,V4L2_CID_AUDIO_LOUDNESS , 0, 1, 1, 0);
ov5640->href = v4l2_ctrl_new_std(&ov5640->ctrl_handler, ops,V4L2_CID_VFLIP, 0, 1, 1, 0);
ov5640->gpclkhref = v4l2_ctrl_new_std(&ov5640->ctrl_handler, ops,V4L2_CID_AUDIO_MUTE , 0, 1, 1, 0);
ov5640->gpclkvsync = v4l2_ctrl_new_std(&ov5640->ctrl_handler, ops,V4L2_CID_HUE_AUTO , 0, 1, 1, 0);
subdev->ctrl_handler = &ov5640->ctrl_handler;
out:
ov5640_s_power(subdev, 0);
return ret;
}
static int ov5640_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
{
struct v4l2_mbus_framefmt *format;
format = v4l2_subdev_get_try_format(subdev, fh->pad, 0);
format->code = MEDIA_BUS_FMT_YUYV8_2X8;/*MEDIA_BUS_FMT_UYVY8_2X8;*/
format->width = ov5640_frmsizes[OV5640_SIZE_VGA].width;
format->height = ov5640_frmsizes[OV5640_SIZE_VGA].height;
format->field = V4L2_FIELD_NONE;
format->colorspace = V4L2_COLORSPACE_JPEG;
return 0;
}
static int ov5640_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
{
return 0;
}
static struct v4l2_subdev_internal_ops ov5640_subdev_internal_ops = {
.registered = ov5640_registered,
.open = ov5640_open,
.close = ov5640_close,
};
static int ov5640_get_resources(struct ov5640 *ov5640, struct device *dev)
{
const struct ov5640_platform_data *pdata = ov5640->pdata;
int ret = 0;
/*ov5640->xvclk = clk_get(dev, pdata->clk_xvclk);
if (IS_ERR(ov5640->xvclk)) {
dev_err(dev, "Unable to get XVCLK (%s)\n", pdata->clk_xvclk);
return -ENODEV;
}
if (clk_round_rate(ov5640->xvclk, 24000000) != 24000000)
dev_warn(dev, "XVCLK set to rounded aproximate (%lu Hz)",
clk_round_rate(ov5640->xvclk, 24000000));
if (clk_set_rate(ov5640->xvclk,
clk_round_rate(ov5640->xvclk, 24000000))) {
dev_err(dev, "Unable to change XVCLK (%s) rate!\n",
pdata->clk_xvclk);
ret = -EINVAL;
goto err_clk_set_rate;
}
*/
/*
if (!pdata->reg_avdd)
goto get_reg_dovdd;
//ov5640->avdd = devm_regulator_get(dev, pdata->reg_avdd);
if (IS_ERR(ov5640->avdd)) {
dev_err(dev, "Unable to get AVDD (%s) regulator\n",
pdata->reg_avdd);
ret = -ENODEV;
goto err_reg_avdd;
}
if (regulator_set_voltage(ov5640->avdd, 2800000, 2800000)) {
dev_err(dev, "Unable to set valid AVDD (%s) regulator"
" voltage to: 2.8V\n", pdata->reg_avdd);
ret = -ENODEV;
goto err_reg_avdd;
}
get_reg_dovdd:
if (!pdata->reg_dovdd)
goto get_gpio_pwdn;
ov5640->dovdd = devm_regulator_get(dev, pdata->reg_dovdd);
if (IS_ERR(ov5640->dovdd)) {
dev_err(dev, "Unable to get DOVDD (%s) regulator\n",
pdata->reg_dovdd);
ret = -ENODEV;
goto err_reg_dovdd;
}
if (regulator_set_voltage(ov5640->dovdd, 1800000, 1800000)) {
dev_err(dev, "Unable to set valid DOVDD (%s) regulator"
" voltage to: 1.8V\n", pdata->reg_dovdd);
ret = -ENODEV;
goto err_reg_dovdd;
}
*//*
get_gpio_pwdn:
if (!gpio_is_valid(pdata->gpio_pwdn))
goto get_gpio_resetb;
if (gpio_request_one(pdata->gpio_pwdn,
pdata->is_gpio_pwdn_acthi ?
GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH,
"OV5640_PWDN")) {
dev_err(dev, "Cannot request GPIO %d\n", pdata->gpio_pwdn);
ret = -ENODEV;
goto err_gpio_pwdn;
}
get_gpio_resetb:
if (!gpio_is_valid(pdata->gpio_resetb))
goto out;
if (gpio_request_one(pdata->gpio_resetb,
pdata->is_gpio_resetb_acthi ?
GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH,
"OV5640_RESETB")) {
dev_err(dev, "Cannot request GPIO %d\n", pdata->gpio_resetb);
ret = -ENODEV;
goto err_gpio_resetb;
}
*/
out:
return 0;
/*
err_gpio_resetb:
if (gpio_is_valid(pdata->gpio_pwdn))
gpio_free(pdata->gpio_pwdn);
err_gpio_pwdn:
err_reg_dovdd:
err_reg_avdd:
err_clk_set_rate:
clk_put(ov5640->xvclk);
*/
return ret;
}
static void ov5640_put_resources(struct ov5640 *ov5640)
{
/*
if (gpio_is_valid(37))
gpio_free(37);
if (gpio_is_valid(38))
gpio_free(38);
*/
//clk_put(ov5640->xvclk);
}
static struct ov5640_platform_data *
ov5640_get_pdata(struct i2c_client *client)
{
struct ov5640_platform_data *pdata;
struct device_node *endpoint;
struct v4l2_of_endpoint *v4l2_endpoint;
dev_dbg(&client->dev, "ov5640_get_pdata invoked\n");
if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node)
return client->dev.platform_data;
dev_dbg(&client->dev, "ov5640_get_pdata: DT Node found\n");
endpoint = of_graph_get_next_endpoint(client->dev.of_node, NULL);
if (!endpoint)
return NULL;
dev_dbg(&client->dev, "ov5640_get_data: endpoint found\n");
pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
v4l2_endpoint = &pdata->endpoint;
v4l2_of_parse_endpoint(endpoint, v4l2_endpoint);
int mclk, pclk;
if (of_property_read_u32(endpoint, "input-clock-freq", &mclk)) {
/*
dev_err(&client->dev, "input-clock-freq property not found\n");
goto err_out;
} else if (pdata->mclk > MT9T11x_MAX_EXT_CLK) {
dev_err(&client->dev, "input-clock-freq property exceed max value\n");
goto err_out;
*/
}
dev_info(&client->dev, "input-clock-freq: %d\n", mclk);
if (of_property_read_u32(endpoint, "pixel-clock-freq", &pclk)) {
/*
dev_err(&client->dev, "pixel-clock-freq property not found\n");
goto err_out;
} else if (pdata->pclk > MT9T11x_MAX_PIXEL_CLK) {
dev_err(&client->dev, "pixel-clock-freq property exceed max value\n");
goto err_out;
*/
}
dev_info(&client->dev, "pixel-clock-freq: %d\n", pclk);
if (v4l2_endpoint->bus_type == V4L2_MBUS_PARALLEL) {
//pdata->flags = pdata->endpoint->bus->parallel->flags;
dev_info(&client->dev,"Bus_type: V4L2_MBUS_PARALLEL %d\n",v4l2_endpoint->bus_type);
dev_info(&client->dev,"Bus_flags: %d\n",v4l2_endpoint->bus.parallel.flags);
}
else {
//pdata->flags = 0;
dev_info(&client->dev,"Bus_type: Something else %d\n",v4l2_endpoint->bus_type);
}
if (!pdata)
goto done;
done:
of_node_put(endpoint);
return pdata;
}
static int ov5640_probe(struct i2c_client *client,
const struct i2c_device_id *did)
{
struct ov5640 *ov5640;
int ret;
/*
if (!client->dev.platform_data) {
dev_err(&client->dev, "No platform data!!\n");
return -ENODEV;
}
*/
ov5640 = kzalloc(sizeof(*ov5640), GFP_KERNEL);
if (!ov5640)
return -ENOMEM;
ov5640->pdata = ov5640_get_pdata(client);//client->dev.platform_data;
ret = ov5640_get_resources(ov5640, &client->dev);
if (ret) {
kfree(ov5640);
return ret;
}
ov5640->format.code = MEDIA_BUS_FMT_YUYV8_2X8;/*MEDIA_BUS_FMT_UYVY8_2X8;*/
ov5640->format.width = ov5640_frmsizes[OV5640_SIZE_VGA].width;
ov5640->format.height = ov5640_frmsizes[OV5640_SIZE_VGA].height;
ov5640->format.field = V4L2_FIELD_NONE;
ov5640->format.colorspace = V4L2_COLORSPACE_JPEG;
ov5640->clk_cfg.sc_pll_prediv = 3;
ov5640->clk_cfg.sc_pll_rdiv = 1;
ov5640->clk_cfg.sc_pll_mult = 84;
ov5640->clk_cfg.sysclk_div = 1;
ov5640->clk_cfg.mipi_div = 1;
v4l2_i2c_subdev_init(&ov5640->subdev, client, &ov5640_subdev_ops);
strlcpy(ov5640->subdev.name, "ov5640", sizeof(ov5640->subdev.name));
ov5640->subdev.internal_ops = &ov5640_subdev_internal_ops;
ov5640->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
ov5640->subdev.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR;
ov5640->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_init(&ov5640->subdev.entity, 1, &ov5640->pad, 0);
if (ret < 0) {
media_entity_cleanup(&ov5640->subdev.entity);
ov5640_put_resources(ov5640);
kfree(ov5640);
}
ret = v4l2_async_register_subdev(&ov5640->subdev);
if (ret)
dev_err(&client->dev, "v4l2 subdev async failed\n");;
dev_info(&client->dev, "%s sensor driver registered !!\n",
ov5640->subdev.name);
return ret;
}
static int ov5640_remove(struct i2c_client *client)
{
struct v4l2_subdev *subdev = i2c_get_clientdata(client);
struct ov5640 *ov5640 = to_ov5640(subdev);
v4l2_ctrl_handler_free(&ov5640->ctrl_handler);
media_entity_cleanup(&subdev->entity);
v4l2_async_unregister_subdev(subdev);
v4l2_device_unregister_subdev(subdev);
ov5640_put_resources(ov5640);
kfree(ov5640);
return 0;
}
static const struct i2c_device_id ov5640_id[] = {
{ "ov5640", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ov5640_id);
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id ov5640_of_match[] = {
{ .compatible = "ti,ov5640", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ov5640_of_match);
#endif
static struct i2c_driver ov5640_i2c_driver = {
.driver = {
.name = "ov5640",
.of_match_table = of_match_ptr(ov5640_of_match), /* Jakebo */
},
.probe = ov5640_probe,
.remove = ov5640_remove,
.id_table = ov5640_id,
};
static int __init ov5640_mod_init(void)
{
return i2c_add_driver(&ov5640_i2c_driver);
}
static void __exit ov5640_mod_exit(void)
{
i2c_del_driver(&ov5640_i2c_driver);
}
module_init(ov5640_mod_init);
module_exit(ov5640_mod_exit);
MODULE_DESCRIPTION("OmniVision OV5640 Camera driver");
MODULE_AUTHOR("Max Wennerfeldt <max.wennerfeldt@gmail.com>");
MODULE_LICENSE("GPL v2");
In my board file I've added the following entries, and as far as I can see the V4L2 interface sets the correct polarity for the V- and HSYNC:
&i2c5 {
status = "okay";
clock-frequency = <100000>;
ov5640@3c {
compatible = "ti,ov5640";
reg = <0x3C>;
gpio-reset = <&gpio2 5 GPIO_ACTIVE_HIGH>;
gpio-pwdn = <&gpio2 6 GPIO_ACTIVE_LOW>;
port {
cam: endpoint {
remote-endpoint = <&vin3a>;
hsync-active = <1>;
vsync-active = <1>;
pclk-sample = <0>;
input-clock-freq = <24000000>;
pixel-clock-freq = <1000000>;
};
};
};
};
&vip2 {
status = "okay";
};
&vin3a {
status = "okay";
endpoint {
slave-mode;
remote-endpoint = <&cam>;
};
};
In the dmesg i can find the following entries:
[ 10.776107] vip 48990000.vip: loading firmware vpdma-1b8.bin [ 10.783949] vpe 489d0000.vpe: loading firmware vpdma-1b8.bin [ 10.809461] vip 48990000.vip: VPDMA firmware loaded [ 10.809769] vpe 489d0000.vpe: Device registered as /dev/video0 When loading the ov5640 module: [ 724.026646] ov5640 4-003c: input-clock-freq: 24000000 [ 724.026660] ov5640 4-003c: pixel-clock-freq: 1000000 [ 724.026669] ov5640 4-003c: Bus_type: V4L2_MBUS_PARALLEL 0 [ 724.026676] ov5640 4-003c: Bus_flags: 405 [ 724.026688] vip2-s0: Port A: Using subdev ov5640 for capture [ 724.036446] vip2-s0: device registered as video1 [ 724.053140] ov5640 4-003c: Detected a OV5640 chip, revision 0 [ 724.063767] ov5640 4-003c: Sensor ID: 5640 [ 724.125095] ov5640 4-003c: Init extra controls [ 724.125135] ov5640 4-003c: ov5640 sensor driver registered !! Checking the device with v4l2-ctl i get thw following:
root:~# v4l2-ctl --all --device=1 Driver Info (not using libv4l2): Driver name : vip Card type : vip Bus info : platform:vip Driver version: 4.4.36 Capabilities : 0x85200001 Video Capture Read/Write Streaming Extended Pix Format Device Capabilities Device Caps : 0x05200001 Video Capture Read/Write Streaming Extended Pix Format Priority: 2 Video input : 0 (camera 1: ok) Video Standard = 0x00ffb0ff PAL-B/B1/G/H/I/D/D1/K NTSC-M/M-JP/M-KR SECAM-B/D/G/H/K/K1/L/Lc Format Video Capture: Width/Height : 640/480 Pixel Format : 'YUYV' Field : None Bytes per Line: 1280 Size Image : 614400 Colorspace : Broadcast NTSC/PAL (SMPTE170M/ITU601) Flags : Crop Capability Video Capture: Bounds : Left 0, Top 0, Width 640, Height 480 Default : Left 0, Top 0, Width 640, Height 480 Pixel Aspect: 1/1 Crop Capability Video Output: Bounds : Left 0, Top 0, Width 640, Height 480 Default : Left 0, Top 0, Width 640, Height 480 Pixel Aspect: 1/1 Crop: Left 0, Top 0, Width 640, Height 480 Selection: crop, Left 0, Top 0, Width 640, Height 480 Selection: crop_default, Left 0, Top 0, Width 640, Height 480 Selection: crop_bounds, Left 0, Top 0, Width 640, Height 480 Selection: compose, Left 0, Top 0, Width 640, Height 480 Selection: compose_default, Left 0, Top 0, Width 640, Height 480 Selection: compose_bounds, Left 0, Top 0, Width 640, Height 480 Selection: crop, Left 0, Top 0, Width 640, Height 480 Selection: crop_default, Left 0, Top 0, Width 640, Height 480 Selection: crop_bounds, Left 0, Top 0, Width 640, Height 480 Selection: compose, Left 0, Top 0, Width 640, Height 480 Selection: compose_default, Left 0, Top 0, Width 640, Height 480 Selection: compose_bounds, Left 0, Top 0, Width 640, Height 480 Streaming Parameters Video Capture: Capabilities : timeperframe Frames per second: 30.000 (30/1) Read buffers : 4 User Controls mute (bool) : default=0 value=0 loudness (bool) : default=0 value=0 white_balance_automatic (bool) : default=0 value=0 horizontal_flip (bool) : default=0 value=0 vertical_flip (bool) : default=0 value=0 hue_automatic (bool) : default=0 value=0 Image Processing Controls pixel_rate (int64) : min=0 max=0 step=1 default=0 value=0 flags=read-only mute (bool) : default=0 value=0 loudness (bool) : default=0 value=0 white_balance_automatic (bool) : default=0 value=0 horizontal_flip (bool) : default=0 value=0 vertical_flip (bool) : default=0 value=0 hue_automatic (bool) : default=0 value=0
The user controls is a hack used to change the camera signal polarity and bitorder in runtime for debugging. As far as I've been able to see, the polarity of the signals have minor impact on the result, and I'm able to verify the signals polarity change using a oscillocope.
At this point I'm hoping that you might could shed some light on if there is anything I've missed that need to be done on the AM572x side of the project in order to use a custom camera. If you need anymore information tell me what you need and I'll provide it!
Best Regards
Max
