Path: news.gmane.org!not-for-mail From: Guennadi Liakhovetski Newsgroups: gmane.comp.video.video4linux Subject: [RFC PATCH 4/8] Add support for the MT9M001 camera Date: Wed, 23 Jan 2008 18:41:48 +0100 (CET) Lines: 710 Approved: news@gmane.org Message-ID: References: NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII X-Trace: ger.gmane.org 1201110157 28881 80.91.229.12 (23 Jan 2008 17:42:37 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Wed, 23 Jan 2008 17:42:37 +0000 (UTC) To: video4linux-list@redhat.com Original-X-From: video4linux-list-bounces@redhat.com Wed Jan 23 18:42:56 2008 Return-path: Envelope-to: rh-video4linux-list@gmane.org Original-Received: from hormel.redhat.com ([209.132.177.30]) by lo.gmane.org with esmtp (Exim 4.50) id 1JHjcT-00035B-5V for rh-video4linux-list@gmane.org; Wed, 23 Jan 2008 18:42:38 +0100 Original-Received: from listman.util.phx.redhat.com (listman.util.phx.redhat.com [10.8.4.110]) by hormel.redhat.com (Postfix) with ESMTP id 9E85472FB4; Wed, 23 Jan 2008 12:42:10 -0500 (EST) Original-Received: from int-mx1.corp.redhat.com (int-mx1.corp.redhat.com [172.16.52.254]) by listman.util.phx.redhat.com (8.13.1/8.13.1) with ESMTP id m0NHg8l9017245 for ; Wed, 23 Jan 2008 12:42:08 -0500 Original-Received: from mx3.redhat.com (mx3.redhat.com [172.16.48.32]) by int-mx1.corp.redhat.com (8.13.1/8.13.1) with ESMTP id m0NHg8vG028189 for ; Wed, 23 Jan 2008 12:42:08 -0500 Original-Received: from mail.gmx.net (mail.gmx.net [213.165.64.20]) by mx3.redhat.com (8.13.1/8.13.1) with SMTP id m0NHfZHu001044 for ; Wed, 23 Jan 2008 12:41:35 -0500 Original-Received: (qmail invoked by alias); 23 Jan 2008 17:41:29 -0000 Original-Received: from p57BD2F1F.dip0.t-ipconnect.de (EHLO axis700.grange) [87.189.47.31] by mail.gmx.net (mp052) with SMTP; 23 Jan 2008 18:41:29 +0100 X-Authenticated: #20450766 X-Provags-ID: V01U2FsdGVkX18JzEdNfKMJtAD7BzHkb2TLaDIMUqlssnktvE95Pt 3/al7Wqsw8NYli Original-Received: from lyakh (helo=localhost) by axis700.grange with local-esmtp (Exim 4.63) (envelope-from ) id 1JHjbg-00029w-Ku for video4linux-list@redhat.com; Wed, 23 Jan 2008 18:41:48 +0100 X-X-Sender: lyakh@axis700.grange In-Reply-To: X-Y-GMX-Trusted: 0 X-RedHat-Spam-Score: 0 X-Scanned-By: MIMEDefang 2.58 on 172.16.52.254 X-Scanned-By: MIMEDefang 2.58 on 172.16.48.32 X-loop: video4linux-list@redhat.com X-BeenThere: video4linux-list@redhat.com X-Mailman-Version: 2.1.5 Precedence: junk List-Id: Linux and Kernel Video List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: video4linux-list-bounces@redhat.com Errors-To: video4linux-list-bounces@redhat.com Xref: news.gmane.org gmane.comp.video.video4linux:36473 Archived-At: This driver supports Micron MT9M001 monochrome and colour cameras. Signed-off-by: Guennadi Liakhovetski --- drivers/media/video/Kconfig | 15 + drivers/media/video/Makefile | 1 + drivers/media/video/mt9m001.c | 649 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 665 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/mt9m001.c diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index dc0dfec..def5246 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -803,6 +803,21 @@ config SOC_CAMERA over a bus like PCI or USB. For example some i2c camera hanging directly on the data bus of an SoC. +config SOC_CAMERA_MT9M001 + tristate "mt9m001 support" + depends on SOC_CAMERA + select SENSORS_PCA9536 if MT9M001_PCA9536_SWITCH + help + This driver supports MT9M001 cameras from Micron, monochrome + and colour models. + +config MT9M001_PCA9536_SWITCH + bool "pca9536 datawidth switch for mt9m001" + depends on SOC_CAMERA_MT9M001 + help + Select this if your MT9M001 camera uses a PCA9536 I2C GPIO + extender to switch between 8 and 10 bit datawidth modes + config VIDEO_PXA27X tristate "PXA27x Quick Capture Interface driver" depends on VIDEO_DEV && PXA27x diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 81b7cd6..c56bfdb 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -118,5 +118,6 @@ obj-$(CONFIG_VIDEO_VIVI) += vivi.o obj-$(CONFIG_VIDEO_PXA27X) += pxa_camera.o obj-$(CONFIG_SOC_CAMERA) += soc_camera.o +obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core diff --git a/drivers/media/video/mt9m001.c b/drivers/media/video/mt9m001.c new file mode 100644 index 0000000..dc4cc8f --- /dev/null +++ b/drivers/media/video/mt9m001.c @@ -0,0 +1,649 @@ +/* + * Driver for MT9M001 CMOS Image Sensor from Micron + * + * Copyright (C) 2008, Guennadi Liakhovetski + * + * 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 +#include +#include +#include + +#include +#include +#include + +/* mt9m001 i2c address 0x5d + * The platform has to define i2c_board_info + * and call i2c_register_board_info() */ + +/* mt9m001 selected register addresses */ +#define MT9M001_CHIP_VERSION 0x00 +#define MT9M001_ROW_START 0x01 +#define MT9M001_COLUMN_START 0x02 +#define MT9M001_WINDOW_HEIGHT 0x03 +#define MT9M001_WINDOW_WIDTH 0x04 +#define MT9M001_HORIZONTAL_BLANKING 0x05 +#define MT9M001_VERTICAL_BLANKING 0x06 +#define MT9M001_OUTPUT_CONTROL 0x07 +#define MT9M001_SHUTTER_WIDTH 0x09 +#define MT9M001_FRAME_RESTART 0x0b +#define MT9M001_SHUTTER_DELAY 0x0c +#define MT9M001_RESET 0x0d +#define MT9M001_READ_OPTIONS1 0x1e +#define MT9M001_READ_OPTIONS2 0x20 +#define MT9M001_GLOBAL_GAIN 0x35 +#define MT9M001_CHIP_ENABLE 0xF1 + +/* must be 32 bit, bitwise coded by reg_to_int and int_to_reg below */ +struct reg_access { + u16 value; + u8 address; + u8 rw; +} __attribute__ ((packed)); + +union reg_access_32 { + struct reg_access reg; + u32 ctrl; +}; + +enum reg_rw { REG_READ = 0, REG_WRITE = 1, }; + +/* +#define reg_to_int(rw, address, value) ({ \ + union reg_access_32 u = {.reg = {value, address, rw}}; \ + u.i32; \ +}) +*/ + +/* rw has to be numerically most significant, because + * maximum, minimum and value are signed */ +#ifdef __LITTLE_ENDIAN +#define reg_to_int(rw, address, value) (((value) & 0xffff) | (((address) & 0xff) << 16) | (((rw) & 0x7f) << 24)) +#else +#define reg_to_int(rw, address, value) ((((value) & 0xffff) << 16) | (((address) & 0xff) << 8) | ((rw) & 0x7f)) +#endif + +static const struct soc_camera_data_format mt9m001_colour_formats[]= { + { + .name = "RGB Bayer (sRGB)", + .depth = 16, + .fourcc = V4L2_PIX_FMT_SBGGR8, + .colorspace = V4L2_COLORSPACE_SRGB, + } +}; + +static const struct soc_camera_data_format mt9m001_monochrome_formats[]= { + { + .name = "Monochrome 10 bit", + .depth = 10, + .fourcc = V4L2_PIX_FMT_Y16, + }, { + .name = "Monochrome 8 bit", + .depth = 8, + .fourcc = V4L2_PIX_FMT_GREY, + }, +}; + +enum mt9m001_model { + MT9M001C12ST, + MT9M001C12STM +}; + +struct mt9m001 { + struct i2c_client *client; + struct soc_camera_device icd; + enum mt9m001_model model; + struct i2c_client *data_switch; + unsigned char autoexposure; + unsigned char datawidth; +}; + +static int reg_read(struct soc_camera_device *icd, const u8 reg) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + struct i2c_client *client = mt9m001->client; + s32 data = i2c_smbus_read_word_data(client, reg); + return data < 0 ? data : swab16(data); +} + +static int reg_write(struct soc_camera_device *icd, const u8 reg, + const u16 data) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + return i2c_smbus_write_word_data(mt9m001->client, reg, swab16(data)); +} + +static int reg_set(struct soc_camera_device *icd, const u8 reg, + const u16 data) +{ + int ret; + + if ((ret = reg_read(icd, reg)) < 0) + return ret; + return reg_write(icd, reg, ret | data); +} + +static int reg_clear(struct soc_camera_device *icd, const u8 reg, + const u16 data) +{ + int ret; + + if ((ret = reg_read(icd, reg)) < 0) + return ret; + return reg_write(icd, reg, ret & ~data); +} + +static int mt9m001_init(struct soc_camera_device *icd) +{ + int ret; + + /* Disable chip, synchronous option update */ + dev_dbg(icd->vdev->dev, "%s\n", __FUNCTION__); + + ret = reg_write(icd, MT9M001_RESET, 1); + if (ret >= 0) + ret = reg_write(icd, MT9M001_RESET, 0); + if (ret >= 0) + ret = reg_write(icd, MT9M001_OUTPUT_CONTROL, 0); + + return ret >= 0 ? 0 : -EIO; +} + +static int mt9m001_release(struct soc_camera_device *icd) +{ + /* Disable the chip */ + reg_write(icd, MT9M001_OUTPUT_CONTROL, 0); + return 0; +} + +static int mt9m001_start_capture(struct soc_camera_device *icd) +{ + /* Switch to master "normal" mode */ + if (reg_write(icd, MT9M001_OUTPUT_CONTROL, 2) < 0) + return -EIO; + return 0; +} + +static int mt9m001_stop_capture(struct soc_camera_device *icd) +{ + /* Stop sensor readout */ + if (reg_write(icd, MT9M001_OUTPUT_CONTROL, 0) < 0) + return -EIO; + return 0; +} + +extern int pca9536_set_level(struct i2c_client *client, u8 pin, u8 level); + +static int external_bus_switch(struct i2c_client *extender, int go8bit) +{ + if (! extender) + return -ENODEV; + +#ifdef CONFIG_MT9M001_PCA9536_SWITCH + return pca9536_set_level(extender, 0, go8bit); +#else + return -ENODEV; +#endif +} + +static int mt9m001_set_capture_format(struct soc_camera_device *icd, __u32 pixfmt, + struct v4l2_rect *rect, unsigned int flags) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + unsigned int width_flag = flags & (IS_DATAWIDTH_10 | IS_DATAWIDTH_9 | + IS_DATAWIDTH_8); + int ret; + const u16 hblank = 9, vblank = 25; + + /* MT9M001 has all capture_format parameters fixed */ + if (! (flags & IS_MASTER) || + ! (flags & IS_PCLK_SAMPLE_RISING) || + ! (flags & IS_HSYNC_ACTIVE_HIGH) || + ! (flags & IS_VSYNC_ACTIVE_HIGH)) { + return -EINVAL; + } + + /* Only one width bit may be set */ + if (!is_power_of_2(width_flag)) + return -EINVAL; + + if ((mt9m001->datawidth != 10 && (width_flag == IS_DATAWIDTH_10)) || + (mt9m001->datawidth != 9 && (width_flag == IS_DATAWIDTH_9)) || + (mt9m001->datawidth != 8 && (width_flag == IS_DATAWIDTH_8))) { + /* data width switch requested */ + if (! mt9m001->data_switch) + return -EINVAL; + + /* Well, we actually only can do 10 or 8 bits... */ + if (width_flag == IS_DATAWIDTH_9) + return -EINVAL; + ret = external_bus_switch(mt9m001->data_switch, + width_flag == IS_DATAWIDTH_8); + if (ret < 0) + return ret; + + mt9m001->datawidth = width_flag == IS_DATAWIDTH_8 ? 8 : 10; + } + + /* Blanking and start values - default... */ + ret = reg_write(icd, MT9M001_HORIZONTAL_BLANKING, hblank); + if (ret >= 0) + ret = reg_write(icd, MT9M001_VERTICAL_BLANKING, vblank); + + /* The caller provides a supported format, as verified per + * call to icd->try_fmt_cap() */ + if (ret >= 0) + ret = reg_write(icd, MT9M001_COLUMN_START, rect->left); + if (ret >= 0) + ret = reg_write(icd, MT9M001_ROW_START, rect->top); + if (ret >= 0) + ret = reg_write(icd, MT9M001_WINDOW_WIDTH, rect->width - 1); + if (ret >= 0) + ret = reg_write(icd, MT9M001_WINDOW_HEIGHT, rect->height + icd->y_skip_top - 1); + if (ret >= 0 && mt9m001->autoexposure) { + ret = reg_write(icd, MT9M001_SHUTTER_WIDTH, rect->height + icd->y_skip_top + vblank); + if (ret >= 0) { + const struct v4l2_queryctrl *qctrl = + soc_camera_find_qctrl(icd->ops, V4L2_CID_EXPOSURE); + icd->exposure = (524 + (rect->height + icd->y_skip_top + vblank - 1) * + (qctrl->maximum - qctrl->minimum)) / + 1048 + qctrl->minimum; + } + } + + return ret < 0 ? ret : 0; +} + +static int mt9m001_try_fmt_cap(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + if (f->fmt.pix.height < 32 + icd->y_skip_top) + f->fmt.pix.height = 32 + icd->y_skip_top; + if (f->fmt.pix.height > 1024 + icd->y_skip_top) + f->fmt.pix.height = 1024 + icd->y_skip_top; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.width > 1280) + f->fmt.pix.width = 1280; + f->fmt.pix.width &= ~0x01; /* has to be even, unsure why was ~3 */ + + return 0; +} + +static unsigned int mt9m001_get_datawidth(struct soc_camera_device *icd) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + return mt9m001->datawidth; +} + +const struct v4l2_queryctrl mt9m001_controls[] = { + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Flip Vertically", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 64, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure", + .minimum = 1, + .maximum = 255, + .step = 1, + .default_value = 255, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .id = V4L2_CID_AUTOEXPOSURE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Automatic Exposure", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, { + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Register access", + .minimum = reg_to_int(REG_READ, 0, 0), + .maximum = reg_to_int(REG_WRITE, 0xff, (1 << 16) - 1), + .step = 1, + .default_value = 1, + } +}; + +static int mt9m001_get_control(struct soc_camera_device *icd, struct v4l2_control *ctrl); +static int mt9m001_set_control(struct soc_camera_device *icd, struct v4l2_control *ctrl); + +static struct soc_camera_ops mt9m001_ops = { + .owner = THIS_MODULE, + .init = mt9m001_init, + .release = mt9m001_release, + .start_capture = mt9m001_start_capture, + .stop_capture = mt9m001_stop_capture, + .set_capture_format = mt9m001_set_capture_format, + .try_fmt_cap = mt9m001_try_fmt_cap, + .formats = NULL, /* Filled in later depending on the */ + .num_formats = 0, /* camera type and data widths */ + .get_datawidth = mt9m001_get_datawidth, + .controls = mt9m001_controls, + .num_controls = ARRAY_SIZE(mt9m001_controls), + .get_control = mt9m001_get_control, + .set_control = mt9m001_set_control, +}; + +static int mt9m001_get_control(struct soc_camera_device *icd, struct v4l2_control *ctrl) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + data = reg_read(icd, MT9M001_READ_OPTIONS2); + if (data < 0) + return -EIO; + ctrl->value = !!(data & 0x8000); + break; + case V4L2_CID_AUTOEXPOSURE: + ctrl->value = mt9m001->autoexposure; + break; + case V4L2_CID_PRIVATE_BASE: + if (1) { + union reg_access_32 u; + + u.ctrl = ctrl->value; + if (u.reg.rw != REG_READ) + return -EINVAL; + data = reg_read(icd, u.reg.address); + if (data < 0) + return -EIO; + u.reg.value = data; + ctrl->value = u.ctrl; + } + } + return 0; +} + +static int mt9m001_set_control(struct soc_camera_device *icd, struct v4l2_control *ctrl) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + const struct v4l2_queryctrl *qctrl; + int data; + + qctrl = soc_camera_find_qctrl(&mt9m001_ops, ctrl->id); + + if (!qctrl) + return -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->value) + data = reg_set(icd, MT9M001_READ_OPTIONS2, 0x8000); + else + data = reg_clear(icd, MT9M001_READ_OPTIONS2, 0x8000); + if (data < 0) + return -EIO; + break; + case V4L2_CID_GAIN: + if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) + return -EINVAL; + /* See Datasheet Table 7, Gain settings. */ + if (ctrl->value <= qctrl->default_value) { + /* Pack it into 0..1 step 0.125, register values 0..8 */ + unsigned long range = qctrl->default_value - qctrl->minimum; + data = ((ctrl->value - qctrl->minimum) * 8 + range / 2) / range; + + printk("Setting gain %d\n", data); + data = reg_write(icd, MT9M001_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } else { + /* Pack it into 1.125..15 variable step, register values 9..67 */ + /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ + unsigned long range = qctrl->maximum - qctrl->default_value - 1; + unsigned long gain = ((ctrl->value - qctrl->default_value - 1) * + 111 + range / 2) / range + 9; + + if (gain <= 32) + data = gain; + else if (gain <= 64) + data = ((gain - 32) * 16 + 16) / 32 + 80; + else + data = ((gain - 64) * 7 + 28) / 56 + 96; + + dev_info(&icd->dev, "Setting gain from %d to %d\n", + reg_read(icd, MT9M001_GLOBAL_GAIN), data); + data = reg_write(icd, MT9M001_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } + + /* Success */ + icd->gain = ctrl->value; + break; + case V4L2_CID_EXPOSURE: + /* mt9m001 has maximum == default */ + if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) + return -EINVAL; + else { + unsigned long range = qctrl->maximum - qctrl->minimum; + unsigned long shutter = ((ctrl->value - qctrl->minimum) * 1048 + + range / 2) / range + 1; + + dev_info(&icd->dev, "Setting shutter width from %d to %lu\n", + reg_read(icd, MT9M001_SHUTTER_WIDTH), shutter); + if (reg_write(icd, MT9M001_SHUTTER_WIDTH, shutter) < 0) + return -EIO; + icd->exposure = ctrl->value; + mt9m001->autoexposure = 0; + } + break; + case V4L2_CID_AUTOEXPOSURE: + if (ctrl->value) { + const u16 vblank = 25; + if (reg_write(icd, MT9M001_SHUTTER_WIDTH, icd->height + + icd->y_skip_top + vblank) < 0) + return -EIO; + qctrl = soc_camera_find_qctrl(icd->ops, V4L2_CID_EXPOSURE); + icd->exposure = (524 + (icd->height + icd->y_skip_top + vblank - 1) * + (qctrl->maximum - qctrl->minimum)) / + 1048 + qctrl->minimum; + mt9m001->autoexposure = 1; + } else + mt9m001->autoexposure = 0; + break; + case V4L2_CID_PRIVATE_BASE: + if (1) { + union reg_access_32 u; + + u.ctrl = ctrl->value; + if (u.reg.rw != REG_WRITE) + return -EINVAL; + data = reg_write(icd, u.reg.address, u.reg.value); + if (data < 0) + return -EIO; + } + } + return 0; +} + +/* Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one */ +static int mt9m001_video_probe(struct soc_camera_device *icd) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + s32 data; + int ret; + + /* We must have a parent by now. And it cannot be a wrong one. + * So this entire test is completely redundant. */ + if (!icd->dev.parent || + to_soc_camera_host(icd->dev.parent)->nr != icd->iface) + return -ENODEV; + + /* Enable the chip */ + data = reg_write(&mt9m001->icd, MT9M001_CHIP_ENABLE, 1); + dev_dbg(&icd->dev, "write: %d\n", data); + + /* Read out the chip version register */ + data = reg_read(icd, MT9M001_CHIP_VERSION); + + /* must be 0x8411 or 0x8421 for colour sensor and 8431 for bw */ + switch (data) { + case 0x8411: + case 0x8421: + mt9m001->model = MT9M001C12ST; + mt9m001_ops.formats = mt9m001_colour_formats; + mt9m001_ops.num_formats = ARRAY_SIZE(mt9m001_colour_formats); + break; + case 0x8431: + mt9m001->model = MT9M001C12STM; + mt9m001_ops.formats = mt9m001_monochrome_formats; + if (mt9m001->client->dev.platform_data) + mt9m001_ops.num_formats = ARRAY_SIZE(mt9m001_monochrome_formats); + else + mt9m001_ops.num_formats = 1; + break; + default: + ret = -ENODEV; + dev_err(&icd->dev, + "No MT9M001 chip detected, register read %x\n", data); + goto ei2c; + } + + dev_info(&icd->dev, "Detected a MT9M001 chip ID %x (%s)\n", data, + data == 0x8431 ? "C12STM" : "C12ST"); + + /* Now that we know the model, we can start video */ + ret = soc_camera_video_start(icd); + if (ret) + goto eisis; + + return 0; + +eisis: +ei2c: + return ret; +} + +static void mt9m001_video_remove(struct soc_camera_device *icd) +{ + struct mt9m001 *mt9m001 = container_of(icd, struct mt9m001, icd); + + dev_dbg(&icd->dev, "Video %x removed: %p, %p\n", mt9m001->client->addr, + mt9m001->icd.dev.parent, mt9m001->icd.vdev); + soc_camera_video_stop(&mt9m001->icd); +} + +static int mt9m001_probe(struct i2c_client *client) +{ + struct mt9m001 *mt9m001; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + pr_debug("%s: I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n", __FUNCTION__); + return -EIO; + } + + if (!(mt9m001 = kzalloc(sizeof(struct mt9m001), GFP_KERNEL))) + return -ENOMEM; + + mt9m001->client = client; + i2c_set_clientdata(client, mt9m001); + + /* Second stage probe - when a capture adapter is there */ + mt9m001->icd.probe = mt9m001_video_probe; + mt9m001->icd.remove = mt9m001_video_remove; + mt9m001->icd.ops = &mt9m001_ops; + mt9m001->icd.control = &client->dev; + mt9m001->icd.x_min = 20; + mt9m001->icd.y_min = 12; + mt9m001->icd.x_current = 20; + mt9m001->icd.y_current = 12; + mt9m001->icd.width_min = 48; + mt9m001->icd.width_max = 1280; + mt9m001->icd.height_min = 32; + mt9m001->icd.height_max = 1024; + mt9m001->icd.y_skip_top = 1; + /* Default datawidth - this is the only width this camera (normally) + * supports. It is only with extra logic that it can support + * other widths. Therefore it seems to be a sensible default. */ + mt9m001->datawidth = 10; + /* Simulated autoexposure. If enabled, we calculate shutter width + * ourselves in the driver based on vertical blanking and frame width */ + mt9m001->autoexposure = 1; + + if (client->dev.platform_data) { + struct soc_camera_link *icl = client->dev.platform_data; + /* We have a data bus switch. We call pca9536 functions + * explicitly by name so the driver will not be unloaded, + * and we'll check mt9m001->data_switch anyway every time + * before calling. The only concern is that the driver + * doesn't detach itself from the device, but so far it is + * not supported by the I2C layer */ + if (icl->extender) + mt9m001->data_switch = *icl->extender; + mt9m001->icd.iface = icl->bus_id; + } + + ret = soc_camera_device_register(&mt9m001->icd); + if (ret) + goto eisdr; + + return 0; + +eisdr: + kfree(mt9m001); + return ret; +} + +static int mt9m001_remove(struct i2c_client *client) +{ + struct mt9m001 *mt9m001 = i2c_get_clientdata(client); + + soc_camera_device_unregister(&mt9m001->icd); + kfree(mt9m001); + + return 0; +} + +static struct i2c_driver mt9m001_i2c_driver = { + .driver = { + .name = "mt9m001", + }, + .probe = mt9m001_probe, + .remove = mt9m001_remove, +}; + +static int __init mt9m001_mod_init(void) +{ + return i2c_add_driver(&mt9m001_i2c_driver); +} + +static void __exit mt9m001_mod_exit(void) +{ + i2c_del_driver(&mt9m001_i2c_driver); +} + +module_init(mt9m001_mod_init); +module_exit(mt9m001_mod_exit); + +MODULE_DESCRIPTION("Micron MT9M001 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL"); -- 1.5.3.4 -- video4linux-list mailing list Unsubscribe mailto:video4linux-list-request@redhat.com?subject=unsubscribe https://www.redhat.com/mailman/listinfo/video4linux-list