[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1383947183-11122-1-git-send-email-marek@goldelico.com>
Date: Fri, 8 Nov 2013 22:46:23 +0100
From: Marek Belisko <marek@...delico.com>
To: m.chehab@...sung.com
Cc: linux-media@...r.kernel.org, linux-kernel@...r.kernel.org,
hns@...delico.com, Marek Belisko <marek@...delico.com>
Subject: [PATCH] media: i2c: Add camera driver for ov9655 chips.
This is a driver for the Omnivision OV9655 camera module
connected to the OMAP3 parallel camera interface and using
the ISP (Image Signal Processor). It supports SXGA and VGA
plus some other modes.
It was tested on gta04 board.
Signed-off-by: Marek Belisko <marek@...delico.com>
Signed-off-by: H. Nikolaus Schaller <hns@...delico.com>
---
drivers/media/i2c/Kconfig | 8 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/ov9655.c | 1205 ++++++++++++++++++++++++++++++++++++++++++++
include/media/ov9655.h | 17 +
4 files changed, 1231 insertions(+)
create mode 100644 drivers/media/i2c/ov9655.c
create mode 100644 include/media/ov9655.h
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 842654d..97d5e4e 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -501,6 +501,14 @@ config VIDEO_OV9650
This is a V4L2 sensor-level driver for the Omnivision
OV9650 and OV9652 camera sensors.
+config VIDEO_OV9655
+ tristate "OmniVision OV9655 sensor support"
+ depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ depends on MEDIA_CAMERA_SUPPORT
+ ---help---
+ This is a Video4Linux2 sensor-level driver for the OmniVision
+ OV9655 image sensor.
+
config VIDEO_VS6624
tristate "ST VS6624 sensor support"
depends on VIDEO_V4L2 && I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index e03f177..367ea01 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -57,6 +57,7 @@ obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o
obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
+obj-$(CONFIG_VIDEO_OV9655) += ov9655.o
obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o
obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o
obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o
diff --git a/drivers/media/i2c/ov9655.c b/drivers/media/i2c/ov9655.c
new file mode 100644
index 0000000..770abfe
--- /dev/null
+++ b/drivers/media/i2c/ov9655.c
@@ -0,0 +1,1205 @@
+/*
+ * Driver for OV9655 CMOS Image Sensor from OmniVision
+ *
+ * H. N. Schaller <hns@...delico.com>
+ *
+ * Based on Driver for MT9P031 CMOS Image Sensor from Aptina
+ *
+ * Copyright (C) 2011, Laurent Pinchart <laurent.pinchart@...asonboard.com>
+ * Copyright (C) 2011, Javier Martin <javier.martin@...ta-silicon.com>
+ * Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@....de>
+ *
+ * Based on the MT9V032 driver and Bastian Hecht's code.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/log2.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/ov9655.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+/* ov9655 register addresses */
+#define OV9655_GAIN 0x00
+#define OV9655_BLUE 0x01
+#define OV9655_RED 0x02
+#define OV9655_VREF 0x03
+#define OV9655_COM1 0x04
+#define OV9655_BAVE 0x05
+#define OV9655_GBAVE 0x06
+#define OV9655_GRAVE 0x07
+#define OV9655_RAVE 0x08
+#define OV9655_COM2 0x09
+#define OV9655_COM2_SLEEP 0x10
+#define OV9655_PID 0x0A
+#define OV9655_CHIP_PID 0x96
+#define OV9655_VER 0x0B
+#define OV9655_CHIP_VER4 0x56
+#define OV9655_CHIP_VER5 0x57
+#define OV9655_COM3 0x0C
+#define OV9655_COM3_SWAP 0x40
+#define OV9655_COM4 0x0D
+#define OV9655_COM5 0x0E
+#define OV9655_COM6 0x0F
+#define OV9655_COM6_TIMING 0x02
+#define OV9655_COM6_WINDOW 0x04
+#define OV9655_AEC 0x10
+#define OV9655_CLKRC 0x11
+#define OV9655_CLKRC_EXT 0x40
+#define OV9655_CLKRC_SCALAR 0x3f
+#define OV9655_COM7 0x12
+#define OV9655_COM7_FMT_MASK 0x07
+#define OV9655_COM7_RAW 0x00
+#define OV9655_COM7_RAW_INT 0x01
+#define OV9655_COM7_YUV 0x02
+#define OV9655_COM7_RGB 0x03
+#define OV9655_COM7_RGB5X5 0x07
+#define OV9655_COM7_RES_MASK 0x70
+#define OV9655_COM7_SXGA 0x00
+#define OV9655_COM7_VGA 0x60
+#define OV9655_COM8 0x13
+#define OV9655_COM8_AGC 0x04
+#define OV9655_COM8_AWB 0x02
+#define OV9655_COM8_AEC 0x01
+#define OV9655_COM9 0x14
+#define OV9655_COM10 0x15
+#define OV9655_COM10_HSYNC_NEG 0x01
+#define OV9655_COM10_VSYNC_NEG 0x02
+#define OV9655_COM10_RESET_END 0x04
+#define OV9655_COM10_HREF_REV 0x08
+#define OV9655_COM10_PCLK_REV 0x10
+#define OV9655_COM10_PCLK_GATE 0x20
+#define OV9655_COM10_HREF2HSYNC 0x40
+#define OV9655_COM10_SLAVE_MODE 0x80
+#define OV9655_REG16 0x16
+#define OV9655_HSTART 0x17
+#define OV9655_HSTOP 0x18
+#define OV9655_VSTART 0x19
+#define OV9655_VSTOP 0x1A
+#define OV9655_PSHFT 0x1B
+#define OV9655_MIDH 0x1C
+#define OV9655_MIDL 0x1D
+#define OV9655_CHIP_MID 0x7fa2
+#define OV9655_MVFP 0x1E
+#define OV9655_MVFP_VFLIP 0x10
+#define OV9655_MVFP_MIRROR 0x20
+#define OV9655_LAEC 0x1F
+#define OV9655_BOS 0x20
+#define OV9655_GBOS 0x21
+#define OV9655_GROS 0x22
+#define OV9655_ROS 0x23
+#define OV9655_AEW 0x24
+#define OV9655_AEB 0x25
+#define OV9655_VPT 0x26
+#define OV9655_BBIAS 0x27
+#define OV9655_GBBIAS 0x28
+#define OV9655_PREGAIN 0x29
+#define OV9655_EXHCH 0x2A
+#define OV9655_EXHCL 0x2B
+#define OV9655_RBIAS 0x2C
+#define OV9655_ADVFL 0x2D
+#define OV9655_ADVFH 0x2E
+#define OV9655_YAVE 0x2F
+#define OV9655_HSYST 0x30
+#define OV9655_HSYEN 0x31
+#define OV9655_HREF 0x32
+#define OV9655_CHLF 0x33
+#define OV9655_AREF1 0x34
+#define OV9655_AREF2 0x35
+#define OV9655_AREF3 0x36
+#define OV9655_ADC1 0x37
+#define OV9655_ADC2 0x38
+#define OV9655_AREF4 0x39
+#define OV9655_TSLB 0x3A
+#define OV9655_TSLB_YUV_MASK 0x0C
+#define OV9655_TSLB_YUYV 0x00
+#define OV9655_TSLB_YVYU 0x04
+#define OV9655_TSLB_VYUY 0x08
+#define OV9655_TSLB_UYVY 0x0C
+#define OV9655_COM11 0x3B
+#define OV9655_COM12 0x3C
+#define OV9655_COM13 0x3D
+#define OV9655_COM14 0x3E
+#define OV9655_COM14_ZOOM 0x02
+#define OV9655_EDGE 0x3F
+#define OV9655_COM15 0x40
+#define OV9655_COM15_RGB_MASK 0x30
+#define OV9655_COM15_RGB 0x00
+#define OV9655_COM15_RGB565 0x10
+#define OV9655_COM15_RGB555 0x30
+#define OV9655_COM16 0x41
+#define OV9655_COM16_SCALING 0x01
+#define OV9655_COM17 0x42
+#define OV9655_MTX1 0x4F
+#define OV9655_MTX2 0x50
+#define OV9655_MTX3 0x51
+#define OV9655_MTX4 0x52
+#define OV9655_MTX5 0x53
+#define OV9655_MTX6 0x54
+#define OV9655_BRTN 0x55
+#define OV9655_CNST1 0x56
+#define OV9655_CNST2 0x57
+#define OV9655_MTXS 0x58
+#define OV9655_AWBOP1 0x59
+#define OV9655_AWBOP2 0x5A
+#define OV9655_AWBOP3 0x5B
+#define OV9655_AWBOP4 0x5C
+#define OV9655_AWBOP5 0x5D
+#define OV9655_AWBOP6 0x5E
+#define OV9655_BLMT 0x5F
+#define OV9655_RLMT 0x60
+#define OV9655_GLMT 0x61
+#define OV9655_LCC1 0x62
+#define OV9655_LCC2 0x63
+#define OV9655_LCC3 0x64
+#define OV9655_LCC4 0x65
+#define OV9655_LCC5 0x66
+#define OV9655_MANU 0x67
+#define OV9655_MANV 0x68
+#define OV9655_BD50MAX 0x6A
+#define OV9655_DBLV 0x6B
+#define OV9655_DBLV_BANDGAP 0x0a
+#define OV9655_DBLV_LDO_BYPASS 0x10
+#define OV9655_DBLV_PLL_BYPASS 0x00
+#define OV9655_DBLV_PLL_4X 0x40
+#define OV9655_DBLV_PLL_6X 0x80
+#define OV9655_DBLV_PLL_8X 0xc0
+#define OV9655_DNSTH 0x70
+#define OV9655_POIDX 0x72
+#define OV9655_POIDX_VDROP 0x40
+#define OV9655_PCKDV 0x73
+#define OV9655_XINDX 0x74
+#define OV9655_YINDX 0x75
+#define OV9655_SLOP 0x7A
+#define OV9655_GAM1 0x7B
+#define OV9655_GAM2 0x7C
+#define OV9655_GAM3 0x7D
+#define OV9655_GAM4 0x7E
+#define OV9655_GAM5 0x7F
+#define OV9655_GAM6 0x80
+#define OV9655_GAM7 0x81
+#define OV9655_GAM8 0x82
+#define OV9655_GAM9 0x83
+#define OV9655_GAM10 0x84
+#define OV9655_GAM11 0x85
+#define OV9655_GAM12 0x86
+#define OV9655_GAM13 0x87
+#define OV9655_GAM14 0x88
+#define OV9655_GAM15 0x89
+#define OV9655_COM18 0x8B
+#define OV9655_COM19 0x8C
+#define OV9655_COM20 0x8D
+#define OV9655_DMLNL 0x92
+#define OV9655_DMNLH 0x93
+#define OV9655_LCC6 0x9D
+#define OV9655_LCC7 0x9E
+#define OV9655_AECH 0xA1
+#define OV9655_BD50 0xA2
+#define OV9655_BD60 0xA3
+#define OV9655_COM21 0xA4
+#define OV9655_GREEN 0xA6
+#define OV9655_VZST 0xA7
+#define OV9655_REFA8 0xA8
+#define OV9655_REFA9 0xA9
+#define OV9655_BLC1 0xAC
+#define OV9655_BLC2 0xAD
+#define OV9655_BLC3 0xAE
+#define OV9655_BLC4 0xAF
+#define OV9655_BLC5 0xB0
+#define OV9655_BLC6 0xB1
+#define OV9655_BLC7 0xB2
+#define OV9655_BLC8 0xB3
+#define OV9655_CTRLB4 0xB4
+#define OV9655_FRSTL 0xB7
+#define OV9655_FRSTH 0xB8
+#define OV9655_ADBOFF 0xBC
+#define OV9655_ADROFF 0xBD
+#define OV9655_ADGBOFF 0xBE
+#define OV9655_ADGROFF 0xBF
+#define OV9655_COM23 0xC4
+#define OV9655_BD60MAX 0xC5
+#define OV9655_COM24 0xC7
+
+/*
+ * pixel clock frequency for 15 fps SXGA
+ * (2 clocks per pixel for byte multiplexing)
+ */
+#define CAMERA_TARGET_FREQ 48000000
+
+/* must be between 10 and 48 MHz or I2C does not work */
+#define CAMERA_EXT_FREQ 24000000
+
+#define OV9655_MAX_WIDTH 1280
+#define OV9655_MIN_WIDTH 2
+#define OV9655_MAX_HEIGHT 1024
+#define OV9655_MIN_HEIGHT 2
+#define OV9655_COLUMN_SKIP 237
+#define OV9655_ROW_SKIP 11
+#define OV9655_LEFT_SKIP 3
+#define OV9655_TOP_SKIP 1
+
+#define OV9655_COLUMS 1520
+#define OV9655_ROWS 1050
+
+#define OV9655_SHUTTER_WIDTH_MIN 1
+#define OV9655_SHUTTER_WIDTH_MAX 1048575
+#define OV9655_SHUTTER_WIDTH_DEF 1943
+#define OV9655_GLOBAL_GAIN_MIN 8
+#define OV9655_GLOBAL_GAIN_MAX 1024
+#define OV9655_GLOBAL_GAIN_DEF 8
+
+/* Number of pixels and number of lines per frame for different standards */
+#define VGA_NUM_ACTIVE_PIXELS (4*160) /* 4:3 */
+#define VGA_NUM_ACTIVE_LINES (3*160)
+#define QVGA_NUM_ACTIVE_PIXELS (VGA_NUM_ACTIVE_PIXELS/2) /* 4:3 */
+#define QVGA_NUM_ACTIVE_LINES (VGA_NUM_ACTIVE_LINES/2)
+#define SXGA_NUM_ACTIVE_PIXELS (5*256) /* 5:4 */
+#define SXGA_NUM_ACTIVE_LINES (4*256)
+#define CIF_NUM_ACTIVE_PIXELS (11*32) /* 11:9 ~ 5:4 */
+#define CIF_NUM_ACTIVE_LINES (9*32)
+
+#define WH(WIDTH, HEIGHT) ((((u32) HEIGHT)<<16)+((u32) WIDTH))
+
+#define VGA WH(VGA_NUM_ACTIVE_PIXELS, VGA_NUM_ACTIVE_LINES)
+#define QVGA WH(QVGA_NUM_ACTIVE_PIXELS, QVGA_NUM_ACTIVE_LINES)
+#define SXGA WH(SXGA_NUM_ACTIVE_PIXELS, SXGA_NUM_ACTIVE_LINES)
+#define CIF WH(CIF_NUM_ACTIVE_PIXELS, CIF_NUM_ACTIVE_LINES)
+
+#define OV9655_PIXEL_ARRAY_WIDTH OV9655_MAX_WIDTH
+#define OV9655_PIXEL_ARRAY_HEIGHT OV9655_MAX_HEIGHT
+#define OV9655_WINDOW_HEIGHT_MIN 2
+#define OV9655_WINDOW_HEIGHT_MAX OV9655_MAX_HEIGHT
+#define OV9655_WINDOW_HEIGHT_DEF OV9655_MAX_HEIGHT
+#define OV9655_WINDOW_WIDTH_MIN 2
+#define OV9655_WINDOW_WIDTH_MAX OV9655_MAX_WIDTH
+#define OV9655_WINDOW_WIDTH_DEF OV9655_MAX_WIDTH
+#define OV9655_ROW_START_MIN 0
+#define OV9655_ROW_START_MAX OV9655_MAX_HEIGHT
+#define OV9655_ROW_START_DEF 0
+#define OV9655_COLUMN_START_MIN 0
+#define OV9655_COLUMN_START_MAX OV9655_MAX_WIDTH
+#define OV9655_COLUMN_START_DEF 0
+
+#define OV9655_FORMAT V4L2_MBUS_FMT_UYVY8_2X8
+
+/* to bulk program camera registers */
+
+struct ov9655_reg {
+ u8 addr;
+ u8 value;
+ /*
+ * mask all bits with this value before setting the (new) value:
+ * newbit = (oldbit & clear) | value; if clear != 0 this is a
+ * read-modify-write command, otherwise
+ * (i.e. not explicitly initialized) a direct write
+ */
+ u8 clear;
+};
+
+/* we assume that the camera is operated as follows:
+ * XCLK is 24 MHz (required to be between 10 MHz and 48 MHz to operate I2C)
+ * PCLK is 48 MHz for SXGA 15 fps and VGA 30 fps, no delay
+ * HSYNC and VSYNC are positive impulses
+ * HSYNC is HSYNC and not HREF
+ * data polarity is not inverted
+ * please make sure that the capture interface is configured accordingly
+ * data format is YUV
+ * SXGA/VGA/QVGA/CIF is asjusted by the format settings
+ */
+
+static const struct ov9655_reg ov9655_init_hardware[] = {
+ { OV9655_COM2, 0x01 },
+ { OV9655_COM10, OV9655_COM10_HREF2HSYNC},
+ { OV9655_CLKRC, 0 },
+ { OV9655_DBLV, OV9655_DBLV_PLL_4X | OV9655_DBLV_BANDGAP },
+};
+
+static const struct ov9655_reg ov9655_init_regs[] = {
+ { OV9655_COM3, 0x00, ~OV9655_COM3_SWAP },
+ { OV9655_COM6, 0x40 },
+ { OV9655_COM7, OV9655_COM7_YUV, ~OV9655_COM7_FMT_MASK },
+ { OV9655_COM11, 0x05 },
+ { OV9655_COM15, 0xc0 },
+};
+
+/* Register values for SXGA format */
+static const struct ov9655_reg ov9655_sxga[] = {
+ { OV9655_COM7, OV9655_COM7_SXGA, ~OV9655_COM7_RES_MASK },
+ { OV9655_HSYEN, 0x50 },
+};
+
+/* Register values for VGA format */
+static const struct ov9655_reg ov9655_vga[] = {
+ { OV9655_COM7, OV9655_COM7_VGA, ~OV9655_COM7_RES_MASK },
+};
+
+/* Register values for QVGA format */
+static const struct ov9655_reg ov9655_qvga[] = {
+ { OV9655_COM7, OV9655_COM7_VGA, ~OV9655_COM7_RES_MASK },
+};
+
+/* Register values for CIF format */
+static const struct ov9655_reg ov9655_cif[] = {
+ { OV9655_COM7, OV9655_COM7_VGA, ~OV9655_COM7_RES_MASK },
+};
+
+/* Register values for YUV format */
+static const struct ov9655_reg ov9655_uyvy_regs[] = {
+ { OV9655_COM7, OV9655_COM7_YUV, ~OV9655_COM7_FMT_MASK },
+ { OV9655_TSLB, OV9655_TSLB_VYUY, ~OV9655_TSLB_YUV_MASK },
+};
+
+/* Register values for RGB format */
+static const struct ov9655_reg ov9655_rgb_regs[] = {
+ { OV9655_COM7, OV9655_COM7_RGB, OV9655_COM7_FMT_MASK },
+ { OV9655_COM15, 0xc0 | OV9655_COM15_RGB555 },
+};
+
+/* Register values for RGB-RAW format */
+static const struct ov9655_reg ov9655_raw_regs[] = {
+ { OV9655_COM7, OV9655_COM7_RAW, OV9655_COM7_FMT_MASK },
+ { OV9655_COM15, 0xc0 | OV9655_COM15_RGB555 },
+};
+
+struct ov9655 {
+ struct v4l2_subdev subdev;
+ struct media_pad pad;
+ struct v4l2_rect crop; /* Sensor window */
+ struct v4l2_mbus_framefmt format;
+ struct ov9655_platform_data *pdata;
+ struct mutex power_lock; /* lock to protect power_count */
+
+ int power_count;
+ int reset; /* reset GPIO number */
+
+ struct clk *clk;
+ struct regulator *vdd;
+
+ struct v4l2_ctrl_handler ctrls;
+ struct v4l2_ctrl *blc_auto;
+ struct v4l2_ctrl *blc_offset;
+};
+
+#define to_ov9655(p) container_of(p, struct ov9655, subdev)
+
+static int ov9655_read(struct i2c_client *client, u8 reg)
+{
+ return i2c_smbus_read_byte_data(client, reg);
+}
+
+static int ov9655_write(struct i2c_client *client, u8 reg, u8 data)
+{
+ return i2c_smbus_write_byte_data(client, reg, data);
+}
+
+static int ov9655_write_regs(struct i2c_client *client,
+ const struct ov9655_reg *regs, const int n)
+{
+ int i, ret;
+ for (i = 0; i < n; i++) {
+ u8 val = regs[i].value;
+ if (regs[i].clear != 0) { /* modify only some bits */
+ ret = ov9655_read(client, regs[i].addr);
+ if (ret < 0)
+ return ret;
+ val |= (ret & regs[i].clear);
+ if (val == (u8) ret)
+ continue; /* no need to write */
+ }
+ ret = ov9655_write(client, regs[i].addr, val);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int ov9655_reset(struct ov9655 *ov9655)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "ov9655_reset\n");
+
+ usleep_range(1000, 2000);
+
+ return ov9655_write_regs(client, ov9655_init_hardware,
+ ARRAY_SIZE(ov9655_init_hardware));
+}
+
+static int ov9655_power_on(struct ov9655 *ov9655)
+{
+ int ret;
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "OV9655 power on\n");
+
+ /* Ensure RESET_BAR is low */
+ if (ov9655->reset != -1) {
+ gpio_set_value(ov9655->reset, 0);
+ usleep_range(1000, 2000);
+ }
+
+ /* Bring up the supplies */
+ ret = regulator_enable(ov9655->vdd);
+ if (ret < 0) {
+ dev_err(&client->dev, "regulator_enable failed err=%d\n", ret);
+ return ret;
+ }
+
+ /* Enable clock */
+ if (ov9655->clk)
+ clk_prepare_enable(ov9655->clk);
+
+ /* Now RESET_BAR must be high */
+ if (ov9655->reset != -1) {
+ gpio_set_value(ov9655->reset, 1);
+ usleep_range(1000, 2000);
+ }
+
+ return 0;
+}
+
+static void ov9655_power_off(struct ov9655 *ov9655)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "OV9655 power off\n");
+
+ if (ov9655->reset != -1) {
+ gpio_set_value(ov9655->reset, 0);
+ usleep_range(1000, 2000);
+ }
+
+ regulator_disable(ov9655->vdd);
+
+ if (ov9655->clk)
+ clk_disable_unprepare(ov9655->clk);
+}
+
+static int __ov9655_set_power(struct ov9655 *ov9655, bool on)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+ int ret;
+
+ if (!on) {
+ ov9655_power_off(ov9655);
+ return 0;
+ }
+
+ ret = ov9655_power_on(ov9655);
+ if (ret < 0)
+ return ret;
+
+ ret = ov9655_reset(ov9655);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to reset the camera\n");
+ return ret;
+ }
+
+ return v4l2_ctrl_handler_setup(&ov9655->ctrls);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev video operations
+ */
+
+static int ov9655_set_params(struct ov9655 *ov9655)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+ struct v4l2_mbus_framefmt *format = &ov9655->format;
+ int ret = 0;
+
+ dev_info(&client->dev, "ov9655_set_params\n");
+
+ /* generic initialization */
+ ov9655_write_regs(client, ov9655_init_regs,
+ ARRAY_SIZE(ov9655_init_regs));
+
+ /* format specific initializations */
+ switch (WH(format->width, format->height)) {
+ case SXGA:
+ ov9655_write_regs(client, ov9655_sxga, ARRAY_SIZE(ov9655_sxga));
+ break;
+ case VGA:
+ ov9655_write_regs(client, ov9655_vga, ARRAY_SIZE(ov9655_vga));
+ break;
+ case QVGA:
+ ov9655_write_regs(client, ov9655_qvga, ARRAY_SIZE(ov9655_qvga));
+ break;
+ case CIF:
+ ov9655_write_regs(client, ov9655_cif, ARRAY_SIZE(ov9655_cif));
+ break;
+ }
+
+ switch (format->code) {
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ ov9655_write_regs(client, ov9655_uyvy_regs,
+ ARRAY_SIZE(ov9655_uyvy_regs));
+ break;
+ case V4L2_MBUS_FMT_RGB565_2X8_BE:
+ ov9655_write_regs(client, ov9655_rgb_regs,
+ ARRAY_SIZE(ov9655_rgb_regs));
+ break;
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ ov9655_write_regs(client, ov9655_raw_regs,
+ ARRAY_SIZE(ov9655_raw_regs));
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int ov9655_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+ int ret;
+
+ dev_dbg(&client->dev, "ov9655_s_stream(%d)\n", enable);
+
+ if (!enable)
+ return 0;
+
+ ret = ov9655_set_params(ov9655);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ov9655_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "ov9655_enum_mbus_code\n");
+
+ if (code->pad || code->index)
+ return -EINVAL;
+
+ code->code = ov9655->format.code;
+ return 0;
+}
+
+static int ov9655_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "ov9655_enum_frame_size\n");
+
+ if (fse->index >= 8 || fse->code != ov9655->format.code)
+ return -EINVAL;
+
+ fse->min_width = OV9655_WINDOW_WIDTH_DEF
+ / min_t(unsigned int, 7, fse->index + 1);
+ fse->max_width = fse->min_width;
+ fse->min_height = OV9655_WINDOW_HEIGHT_DEF / (fse->index + 1);
+ fse->max_height = fse->min_height;
+
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+__ov9655_get_pad_format(struct ov9655 *ov9655, struct v4l2_subdev_fh *fh,
+ unsigned int pad, u32 which)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "__ov9655_get_pad_format pad=%u which=%u\n", pad,
+ which);
+
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(fh, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &ov9655->format;
+ default:
+ return NULL;
+ }
+}
+
+static struct v4l2_rect *
+__ov9655_get_pad_crop(struct ov9655 *ov9655, struct v4l2_subdev_fh *fh,
+ unsigned int pad, u32 which)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "__ov9655_get_pad_crop pad=%u which=%u\n", pad,
+ which);
+
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_crop(fh, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &ov9655->crop;
+ default:
+ return NULL;
+ }
+}
+
+static int ov9655_get_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "ov9655_get_format\n");
+
+ fmt->format = *__ov9655_get_pad_format(ov9655, fh, fmt->pad,
+ fmt->which);
+ return 0;
+}
+
+static int ov9655_set_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *format)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+ struct v4l2_mbus_framefmt *__format;
+ struct v4l2_rect *__crop;
+ unsigned int width;
+ unsigned int height;
+ unsigned int hratio;
+ unsigned int vratio;
+
+ dev_dbg(&client->dev, "ov9655_set_format\n");
+
+ __crop = __ov9655_get_pad_crop(ov9655, fh, format->pad,
+ format->which);
+
+ /* Clamp the width and height to avoid dividing by zero. */
+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2),
+ max(__crop->width / 7, OV9655_WINDOW_WIDTH_MIN),
+ __crop->width);
+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2),
+ max(__crop->height / 8, OV9655_WINDOW_HEIGHT_MIN),
+ __crop->height);
+
+ hratio = DIV_ROUND_CLOSEST(__crop->width, width);
+ vratio = DIV_ROUND_CLOSEST(__crop->height, height);
+
+ /*
+ * take what has been defined for the pad
+ * by user space command e.g.
+ * media-ctl -V '"ov9655 2-0030":0 [UYVY2x8 1024x1024]'
+ */
+
+ __format = __ov9655_get_pad_format(ov9655, fh, format->pad,
+ format->which);
+ __format->width = __crop->width / hratio;
+ __format->height = __crop->height / vratio;
+
+ format->format = *__format;
+
+ switch (WH(__format->width, __format->height)) {
+ case SXGA:
+ case VGA:
+ case QVGA:
+ case CIF:
+ break;
+ default:
+ dev_err(&client->dev,
+ "unknown format width=%u height=%u\n",
+ __format->width, __format->height);
+ return -EINVAL;
+ }
+
+ switch (__format->code) {
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ case V4L2_MBUS_FMT_RGB565_2X8_BE:
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ break;
+ default:
+ dev_err(&client->dev,
+ "unknown format code=%08x\n", __format->code);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ov9655_get_crop(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_crop *crop)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+
+ dev_dbg(&client->dev, "ov9655_get_crop\n");
+
+ crop->rect = *__ov9655_get_pad_crop(ov9655, fh, crop->pad,
+ crop->which);
+ return 0;
+}
+
+static int ov9655_set_crop(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_crop *crop)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ struct i2c_client *client = v4l2_get_subdevdata(&ov9655->subdev);
+ struct v4l2_mbus_framefmt *__format;
+ struct v4l2_rect *__crop;
+ struct v4l2_rect rect;
+
+ dev_dbg(&client->dev, "ov9655_set_crop\n");
+
+ /* Clamp the crop rectangle boundaries and align them to a multiple of 2
+ * pixels to ensure a GRBG Bayer pattern.
+ */
+ rect.left = clamp(ALIGN(crop->rect.left, 2), OV9655_COLUMN_START_MIN,
+ OV9655_COLUMN_START_MAX);
+ rect.top = clamp(ALIGN(crop->rect.top, 2), OV9655_ROW_START_MIN,
+ OV9655_ROW_START_MAX);
+ rect.width = clamp(ALIGN(crop->rect.width, 2),
+ OV9655_WINDOW_WIDTH_MIN,
+ OV9655_WINDOW_WIDTH_MAX);
+ rect.height = clamp(ALIGN(crop->rect.height, 2),
+ OV9655_WINDOW_HEIGHT_MIN,
+ OV9655_WINDOW_HEIGHT_MAX);
+
+ rect.width = min(rect.width, OV9655_PIXEL_ARRAY_WIDTH - rect.left);
+ rect.height = min(rect.height, OV9655_PIXEL_ARRAY_HEIGHT - rect.top);
+
+ __crop = __ov9655_get_pad_crop(ov9655, fh, crop->pad, crop->which);
+
+ if (rect.width != __crop->width || rect.height != __crop->height) {
+ /*
+ * Reset the output image size if the crop rectangle size has
+ * been modified.
+ */
+ __format = __ov9655_get_pad_format(ov9655, fh, crop->pad,
+ crop->which);
+ __format->width = rect.width;
+ __format->height = rect.height;
+ }
+
+ *__crop = rect;
+ crop->rect = rect;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev control operations
+ */
+
+#define V4L2_CID_BLC_AUTO (V4L2_CID_USER_BASE | 0x1002)
+#define V4L2_CID_BLC_TARGET_LEVEL (V4L2_CID_USER_BASE | 0x1003)
+#define V4L2_CID_BLC_ANALOG_OFFSET (V4L2_CID_USER_BASE | 0x1004)
+#define V4L2_CID_BLC_DIGITAL_OFFSET (V4L2_CID_USER_BASE | 0x1005)
+
+static int ov9655_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct ov9655 *ov9655 =
+ container_of(ctrl->handler, struct ov9655, ctrls);
+ int ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ if (!ctrl->val) {
+ /* Restore the black level compensation settings. */
+ if (ov9655->blc_auto->cur.val != 0) {
+ ret = ov9655_s_ctrl(ov9655->blc_auto);
+ if (ret < 0)
+ return ret;
+ }
+ if (ov9655->blc_offset->cur.val != 0) {
+ ret = ov9655_s_ctrl(ov9655->blc_offset);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct v4l2_ctrl_ops ov9655_ctrl_ops = {
+ .s_ctrl = ov9655_s_ctrl,
+};
+
+static const char * const ov9655_test_pattern_menu[] = {
+ "Disabled",
+ "Color Field",
+ "Horizontal Gradient",
+ "Vertical Gradient",
+ "Diagonal Gradient",
+ "Classic Test Pattern",
+ "Walking 1s",
+ "Monochrome Horizontal Bars",
+ "Monochrome Vertical Bars",
+ "Vertical Color Bars",
+};
+
+static const struct v4l2_ctrl_config ov9655_ctrls[] = {
+ {
+ .ops = &ov9655_ctrl_ops,
+ .id = V4L2_CID_BLC_AUTO,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "BLC, Auto",
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 1,
+ .flags = 0,
+ }, {
+ .ops = &ov9655_ctrl_ops,
+ .id = V4L2_CID_BLC_TARGET_LEVEL,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "BLC Target Level",
+ .min = 0,
+ .max = 4095,
+ .step = 1,
+ .def = 168,
+ .flags = 0,
+ }, {
+ .ops = &ov9655_ctrl_ops,
+ .id = V4L2_CID_BLC_ANALOG_OFFSET,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "BLC Analog Offset",
+ .min = -255,
+ .max = 255,
+ .step = 1,
+ .def = 32,
+ .flags = 0,
+ }, {
+ .ops = &ov9655_ctrl_ops,
+ .id = V4L2_CID_BLC_DIGITAL_OFFSET,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "BLC Digital Offset",
+ .min = -2048,
+ .max = 2047,
+ .step = 1,
+ .def = 40,
+ .flags = 0,
+ }
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev core operations
+ */
+
+static int ov9655_set_power(struct v4l2_subdev *subdev, int on)
+{
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ int ret = 0;
+
+ mutex_lock(&ov9655->power_lock);
+
+ /* If the power count is modified from 0 to != 0 or from != 0 to 0,
+ * update the power state.
+ */
+ if (ov9655->power_count == !on) {
+ ret = __ov9655_set_power(ov9655, !!on);
+ if (ret < 0)
+ goto out;
+ }
+
+ /* Update the power count. */
+ ov9655->power_count += on ? 1 : -1;
+ WARN_ON(ov9655->power_count < 0);
+
+out:
+ mutex_unlock(&ov9655->power_lock);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev internal operations
+ */
+
+static int ov9655_registered(struct v4l2_subdev *subdev)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(subdev);
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+ s32 data;
+ int ret;
+
+ ret = ov9655_power_on(ov9655);
+ if (ret < 0) {
+ dev_err(&client->dev, "OV9655 power up failed\n");
+ return ret;
+ }
+
+ /* Read chip manufacturer register */
+ data = (ov9655_read(client, OV9655_MIDH) << 8) +
+ ov9655_read(client, OV9655_MIDL);
+
+ if (data < 0) {
+ dev_err(&client->dev,
+ "OV9655 not detected, can't read manufacturer id\n");
+ return -ENODEV;
+ }
+
+ if (data != OV9655_CHIP_MID) {
+ dev_err(&client->dev,
+ "OV9655 not detected, wrong manufacturer 0x%04x\n",
+ (unsigned) data);
+ return -ENODEV;
+ }
+
+ data = ov9655_read(client, OV9655_PID);
+ if (data != OV9655_CHIP_PID) {
+ dev_err(&client->dev,
+ "OV9655 not detected, wrong part 0x%02x\n",
+ (unsigned) data);
+ return -ENODEV;
+ }
+
+ data = ov9655_read(client, OV9655_VER);
+ if (data != OV9655_CHIP_VER4 && data != OV9655_CHIP_VER5) {
+ dev_err(&client->dev,
+ "OV9655 not detected, wrong version 0x%02x\n",
+ (unsigned) data);
+ return -ENODEV;
+ }
+
+ ov9655_power_off(ov9655);
+
+ dev_info(&client->dev, "OV9655 detected at address 0x%02x\n",
+ client->addr);
+
+ return ret;
+}
+
+static int ov9655_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *crop;
+
+ crop = v4l2_subdev_get_try_crop(fh, 0);
+ crop->left = OV9655_COLUMN_START_DEF;
+ crop->top = OV9655_ROW_START_DEF;
+ crop->width = OV9655_WINDOW_WIDTH_DEF;
+ crop->height = OV9655_WINDOW_HEIGHT_DEF;
+
+ format = v4l2_subdev_get_try_format(fh, 0);
+
+ format->code = OV9655_FORMAT;
+
+ format->width = OV9655_WINDOW_WIDTH_DEF;
+ format->height = OV9655_WINDOW_HEIGHT_DEF;
+ format->field = V4L2_FIELD_NONE;
+ format->colorspace = V4L2_COLORSPACE_SRGB;
+
+ return ov9655_set_power(subdev, 1);
+}
+
+static int ov9655_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
+{
+ return ov9655_set_power(subdev, 0);
+}
+
+static struct v4l2_subdev_core_ops ov9655_subdev_core_ops = {
+ .s_power = ov9655_set_power,
+};
+
+static struct v4l2_subdev_video_ops ov9655_subdev_video_ops = {
+ .s_stream = ov9655_s_stream,
+};
+
+static struct v4l2_subdev_pad_ops ov9655_subdev_pad_ops = {
+ .enum_mbus_code = ov9655_enum_mbus_code,
+ .enum_frame_size = ov9655_enum_frame_size,
+ .get_fmt = ov9655_get_format,
+ .set_fmt = ov9655_set_format,
+ .get_crop = ov9655_get_crop,
+ .set_crop = ov9655_set_crop,
+};
+
+static struct v4l2_subdev_ops ov9655_subdev_ops = {
+ .core = &ov9655_subdev_core_ops,
+ .video = &ov9655_subdev_video_ops,
+ .pad = &ov9655_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops ov9655_subdev_internal_ops = {
+ .registered = ov9655_registered,
+ .open = ov9655_open,
+ .close = ov9655_close,
+};
+
+/* -----------------------------------------------------------------------------
+ * Driver initialization and probing
+ */
+
+static int ov9655_probe(struct i2c_client *client,
+ const struct i2c_device_id *did)
+{
+ struct ov9655_platform_data *pdata = client->dev.platform_data;
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct ov9655 *ov9655;
+ unsigned int i;
+ int ret;
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "No platform data\n");
+ return -EINVAL;
+ }
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_warn(&client->dev,
+ "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n");
+ return -EIO;
+ }
+
+ ov9655 = devm_kzalloc(&client->dev, sizeof(*ov9655), GFP_KERNEL);
+ if (ov9655 == NULL)
+ return -ENOMEM;
+
+ ov9655->pdata = pdata;
+ ov9655->reset = -1;
+
+ ov9655->vdd = devm_regulator_get(&client->dev, "vaux3");
+
+ if (IS_ERR(ov9655->vdd)) {
+ dev_err(&client->dev, "Unable to get regulator\n");
+ return -ENODEV;
+ }
+
+ v4l2_ctrl_handler_init(&ov9655->ctrls, ARRAY_SIZE(ov9655_ctrls) + 6);
+
+ /* register custom controls */
+ v4l2_ctrl_new_std(&ov9655->ctrls, &ov9655_ctrl_ops,
+ V4L2_CID_EXPOSURE, OV9655_SHUTTER_WIDTH_MIN,
+ OV9655_SHUTTER_WIDTH_MAX, 1,
+ OV9655_SHUTTER_WIDTH_DEF);
+ v4l2_ctrl_new_std(&ov9655->ctrls, &ov9655_ctrl_ops,
+ V4L2_CID_GAIN, OV9655_GLOBAL_GAIN_MIN,
+ OV9655_GLOBAL_GAIN_MAX, 1, OV9655_GLOBAL_GAIN_DEF);
+ v4l2_ctrl_new_std(&ov9655->ctrls, &ov9655_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&ov9655->ctrls, &ov9655_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&ov9655->ctrls, &ov9655_ctrl_ops,
+ V4L2_CID_PIXEL_RATE, CAMERA_TARGET_FREQ,
+ CAMERA_TARGET_FREQ, 1, CAMERA_TARGET_FREQ);
+ v4l2_ctrl_new_std_menu_items(&ov9655->ctrls, &ov9655_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(ov9655_test_pattern_menu) - 1, 0,
+ 0, ov9655_test_pattern_menu);
+
+ for (i = 0; i < ARRAY_SIZE(ov9655_ctrls); ++i)
+ v4l2_ctrl_new_custom(&ov9655->ctrls, &ov9655_ctrls[i], NULL);
+
+ ov9655->subdev.ctrl_handler = &ov9655->ctrls;
+
+ if (ov9655->ctrls.error) {
+ dev_err(&client->dev, "control initialization error %d\n",
+ ov9655->ctrls.error);
+ ret = ov9655->ctrls.error;
+ goto done;
+ }
+
+ ov9655->blc_auto = v4l2_ctrl_find(&ov9655->ctrls, V4L2_CID_BLC_AUTO);
+ ov9655->blc_offset = v4l2_ctrl_find(&ov9655->ctrls,
+ V4L2_CID_BLC_DIGITAL_OFFSET);
+
+ mutex_init(&ov9655->power_lock);
+ v4l2_i2c_subdev_init(&ov9655->subdev, client, &ov9655_subdev_ops);
+ ov9655->subdev.internal_ops = &ov9655_subdev_internal_ops;
+
+ ov9655->pad.flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_init(&ov9655->subdev.entity, 1, &ov9655->pad, 0);
+ if (ret < 0)
+ goto done;
+
+ ov9655->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ ov9655->crop.width = OV9655_WINDOW_WIDTH_DEF;
+ ov9655->crop.height = OV9655_WINDOW_HEIGHT_DEF;
+ ov9655->crop.left = OV9655_COLUMN_START_DEF;
+ ov9655->crop.top = OV9655_ROW_START_DEF;
+
+ ov9655->format.code = OV9655_FORMAT;
+
+ ov9655->format.width = OV9655_WINDOW_WIDTH_DEF;
+ ov9655->format.height = OV9655_WINDOW_HEIGHT_DEF;
+ ov9655->format.field = V4L2_FIELD_NONE;
+ ov9655->format.colorspace = V4L2_COLORSPACE_SRGB;
+
+ if (pdata->reset != -1) {
+ ret = devm_gpio_request_one(&client->dev, pdata->reset,
+ GPIOF_OUT_INIT_LOW, "ov9655_rst");
+ if (ret < 0)
+ goto done;
+
+ ov9655->reset = pdata->reset;
+ }
+
+ ov9655->clk = devm_clk_get(&client->dev, NULL);
+ if (IS_ERR(ov9655->clk))
+ return PTR_ERR(ov9655->clk);
+
+ clk_set_rate(ov9655->clk, CAMERA_EXT_FREQ /* pdata->ext_freq */);
+
+ ret = 0;
+
+done:
+ if (ret < 0) {
+ v4l2_ctrl_handler_free(&ov9655->ctrls);
+ media_entity_cleanup(&ov9655->subdev.entity);
+ }
+
+ return ret;
+}
+
+static int ov9655_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov9655 *ov9655 = to_ov9655(subdev);
+
+ v4l2_ctrl_handler_free(&ov9655->ctrls);
+ v4l2_device_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+
+ return 0;
+}
+
+static const struct i2c_device_id ov9655_id[] = {
+ { "ov9655", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ov9655_id);
+
+static struct i2c_driver ov9655_i2c_driver = {
+ .driver = {
+ .name = "ov9655",
+ },
+ .probe = ov9655_probe,
+ .remove = ov9655_remove,
+ .id_table = ov9655_id,
+};
+
+module_i2c_driver(ov9655_i2c_driver);
+
+MODULE_DESCRIPTION("OmniVision OV9655 Camera driver");
+MODULE_AUTHOR("H. Nikolaus Schaller <hns@...delico.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/media/ov9655.h b/include/media/ov9655.h
new file mode 100644
index 0000000..38c1253
--- /dev/null
+++ b/include/media/ov9655.h
@@ -0,0 +1,17 @@
+#ifndef __OV9655_H
+#define __OV9655_H
+
+struct v4l2_subdev;
+
+/*
+ * struct ov9655_platform_data - OV9655 platform data
+ * @reset: Chip reset GPIO (set to -1 if not used)
+ * @ext_freq: Input clock frequency - not used OV9655 is running at fixed 24 MHz
+ */
+struct ov9655_platform_data {
+ int reset;
+ int ext_freq;
+};
+
+#endif
+
--
1.8.1.2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists