From 4eb6a924fddd774babb001ea333161ed84d01a43 Mon Sep 17 00:00:00 2001 From: =?utf-8?q?Lars=20H=C3=A4ring?= Date: Fri, 28 Mar 2008 17:55:04 +0100 Subject: [PATCH 5/5] =?utf-8?q?Adds=20driver=20for=20the=20Micron=20MT9M112=20camera. =20Signed-off-by:=20Lars=20H=C3=A4ring=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit --- drivers/media/video/Kconfig | 7 + drivers/media/video/Makefile | 1 + drivers/media/video/tm13m3.c | 631 ++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c-id.h | 1 + 4 files changed, 640 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/tm13m3.c diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 63a8a84..d5707bc 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -20,6 +20,13 @@ config VIDEO_AVR32_ISI This module makes the AVR32 Image Sensor Interface available as a v4l2 device. +config VIDEO_MT9M112 + tristate "Micron MT9M112 camera" + default n + depends on VIDEO_AVR32_ISI && I2C + ---help--- + This will add support for the Micron MT9M112 camera. + config VIDEO_ADV_DEBUG bool "Enable advanced debug functionality" default n diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index b605cee..7417dd5 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -81,6 +81,7 @@ obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o obj-$(CONFIG_VIDEO_DPC) += dpc7146.o obj-$(CONFIG_TUNER_3036) += tuner-3036.o obj-$(CONFIG_VIDEO_AVR32_ISI) += atmel-isi.o +obj-$(CONFIG_VIDEO_MT9M112) += tm13m3.o obj-$(CONFIG_VIDEO_TUNER) += tuner.o obj-$(CONFIG_VIDEO_BUF) += video-buf.o diff --git a/drivers/media/video/tm13m3.c b/drivers/media/video/tm13m3.c new file mode 100644 index 0000000..5de88fa --- /dev/null +++ b/drivers/media/video/tm13m3.c @@ -0,0 +1,631 @@ +/* + * Micron Mt9M112 camera driver. + * + * Copyright (C) 2005-2007 Atmel Corporation + * + * 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. + */ +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "atmel-isi.h" + +/* camera standby pin */ +#define CAM_STANDBY GPIO_PIN_PA(9) +/* camera reset pin */ +#define CAM_RESET GPIO_PIN_PA(8) + +/*! Maximum number of pixels in a row */ +#define TM13M3_MAX_WIDTH 1280 +/*! Maximum number of rows */ +#define TM13M3_MAX_HEIGHT 1024 + +/*! Clock for image sensor CLKIN signal */ +static char mclk_name[32] = "gclk0"; +/*! Parent clock of gclk0 + * Either osc0 or pll0 + * We use osc0 with 20MHz quarz. + */ +static char mclk_parent_name[32] = "osc0"; + +static struct clk *mclk; +static struct clk *mclk_parent; +module_param_string(mclk, mclk_name, sizeof(mclk_name), 0644); +MODULE_PARM_DESC(mclk, "Name of the clock used as camera clock input"); + +module_param_string(mclk_parent, mclk_parent_name, + sizeof(mclk_parent_name), 0644); +MODULE_PARM_DESC(mclk, "Name of mclk parent clock"); + + +/* Register adresses */ +#define CHIP_VERSION 0x0 +#define PROGRAM_CONTROL 0x2CC +#define READ_MODE_CONTEXT_B 0x20 +#define CONTEXT_CONTROL 0xC8 +#define COLUMN_WIDTH 0x4 +#define HORIZONTAL_OUTPUT_SIZE_B 0x1A1 +#define VERTICAL_OUTPUT_SIZE_B 0x1A4 +#define ROW_WIDTH 0x3 +#define PAGE_MAP 0xF0 +#define OUTPUT_FORMAT_CONTROL_A 0x13A +#define HORIZONTAL_ZOOM 0x1A6 +#define VERTICAL_ZOOM 0x1A9 +#define PLL_CONTROL_1 0x66 +#define PLL_CONTROL_2 0x67 +#define CLOCK_CONTROL 0x65 + +/* Chip ID stored in CHIP_VERSION register */ +#define MT9M112_CHIP_ID 0x148C +/* I2C address of camera module */ +#define I2C_TM13M3 0x5D + +static unsigned short normal_i2c[] = { + I2C_TM13M3, + I2C_CLIENT_END +}; +I2C_CLIENT_INSMOD; + +#ifdef CONFIG_DEBUG_FS +struct reg_dbg { + struct tm13m3 *is; + struct dentry *dentry; + unsigned int offset; +}; +#endif + +struct tm13m3 { + struct mutex mutex; + u16 current_page; + u32 current_format; + u16 pll_avr_ctrl; + struct clk *mclk; + struct i2c_client client; + struct atmel_isi_camera cam; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; + struct reg_dbg debugfs_reg[37]; +#endif +}; + + +#define to_tm13m3(cam) container_of(cam, struct tm13m3, cam) + +static struct i2c_driver tm13m3_driver; + +static int tm13m3_write_16(struct tm13m3 *is, u16 reg, u16 value) +{ + int ret = 0; + u16 register_page = 0; + + register_page = reg >> 8; + + if ((register_page != is->current_page) + && (reg != PAGE_MAP)){ + + if( 0 <= (ret = i2c_smbus_write_word_data(&is->client, PAGE_MAP, cpu_to_le16(register_page)))) + is->current_page = register_page; + } + + if(ret >= 0){ + ret = i2c_smbus_write_word_data(&is->client, (u8) reg, cpu_to_le16(value)); + } + return ret; +} + +static int tm13m3_read_16(struct tm13m3 *is, u16 reg) +{ + int ret = 0; + u16 register_page = 0; + + register_page = reg >> 8; + + if ((register_page != is->current_page) + && (reg != PAGE_MAP)){ + + if( 0 <= (ret = i2c_smbus_write_word_data(&is->client, PAGE_MAP, cpu_to_le16(register_page)))) + is->current_page = register_page; + } + +if(ret >= 0){ + ret = i2c_smbus_read_word_data(&is->client, (u8) reg); + } + + if (ret < 0) + return -EIO; + + return le16_to_cpu(ret); +} + +#ifdef CONFIG_DEBUG_FS +#include +#include + +struct tm13m3_reg { + u16 address; + const char *name; +}; + +static struct tm13m3_reg tm13m3_registers[38] = { + { .address = CHIP_VERSION, .name = "chip_version"}, + { .address = 0x1, .name = "row_start"}, + { .address = 0x2, .name = "column_start"}, + { .address = 0x3, .name = "row_width"}, + { .address = 0x4, .name = "column_width"}, + { .address = 0x5, .name = "horizontal_blanking_b"}, + { .address = 0x6, .name = "vertical_blanking_b"}, + { .address = 0x7, .name = "horizontal_blanking_a"}, + { .address = 0x8, .name = "vertical_blanking_a"}, + { .address = 0x0D, .name = "reset"}, + { .address = 0x20, .name = "read_mode_context_b"}, + { .address = 0x21, .name = "read_mode_context_a"}, + { .address = 0x22, .name = "dark_col_row"}, + { .address = 0x65, .name = "clock_control"}, + { .address = 0x66, .name = "pll_control_1"}, + { .address = 0x67, .name = "pll_control_2"}, + { .address = 0xC8, .name = "context_control"}, + { .address = 0x106, .name = "mode_control"}, + { .address = 0x108, .name = "format_control"}, + { .address = 0x13A, .name = "output_format_control_a"}, + { .address = 0x148, .name = "test_pattern_generator"}, + { .address = 0x19B, .name = "output_format_control_b"}, + { .address = 0x1A1, .name = "horizontal_output_size_b"}, + { .address = 0x1A4, .name = "vertical_output_size_b"}, + { .address = 0x1A5, .name = "horizontal_pan"}, + { .address = 0x1A6, .name = "horizontal_zoom"}, + { .address = 0x1A7, .name = "horizontal_output_size_a"}, + { .address = 0x1A8, .name = "vertical_pan"}, + { .address = 0x1A9, .name = "vertical_zoom"}, + { .address = 0x1AA, .name = "vertical_output_size_a"}, + { .address = 240, .name = "page_map"}, + { .address = 0x2C8, .name = "global_context_control"}, + { .address = 0x2CB, .name = "program_advance"}, + { .address = 0x2CC, .name = "program_control"}, + { .address = 0x2D2, .name = "default_program_conf"}, + { .address = 0x2D3, .name = "user_global_context_control"}, + { .address = (0x100 | 0), .name = "module_id"}, + { .address = (0x200 | 2), .name = "mode_control"}, +}; + +static u64 reg_dbg_get(void *data) +{ + struct reg_dbg *reg = data; + int ret = 0; + + mutex_lock(®->is->mutex); + ret = tm13m3_read_16(reg->is, tm13m3_registers[reg->offset].address); + mutex_unlock(®->is->mutex); + + if (ret < 0) { + printk("%s: failed to read reg 0x%02x: %d\n", + reg->is->cam.name, + tm13m3_registers[reg->offset].address, ret); + return ~0ULL; + } + return ret; +} + +static void reg_dbg_set(void *data, u64 val) +{ + struct reg_dbg *reg = data; + int ret = 0; + + mutex_lock(®->is->mutex); + ret = tm13m3_write_16(reg->is, tm13m3_registers[reg->offset].address, (u16) val); + mutex_unlock(®->is->mutex); + + if (ret < 0){ + printk("%s: failed to write reg 0x%02x: %d\n", + reg->is->cam.name, + tm13m3_registers[reg->offset].address, ret); + } +} +DEFINE_SIMPLE_ATTRIBUTE(reg_dbg_fops, reg_dbg_get, reg_dbg_set, "%04llx\n"); + +static void tm13m3_init_debugfs(struct tm13m3 *is) +{ + struct dentry *root, *reg; + unsigned int i; + + root = debugfs_create_dir(is->cam.name, NULL); + if (IS_ERR(root) || !root) + goto err_root; + is->debugfs_root = root; + + for (i = 0; i < ARRAY_SIZE(is->debugfs_reg); i++) { + if (!tm13m3_registers[i].name) + continue; + + is->debugfs_reg[i].is = is; + is->debugfs_reg[i].offset = i; + + reg = debugfs_create_file(tm13m3_registers[i].name, S_IRUGO | S_IWUSR, + root, &is->debugfs_reg[i], + ®_dbg_fops); + if (!reg) + goto err_reg; + is->debugfs_reg[i].dentry = reg; + } + + return; + +err_reg: + while (i--) + debugfs_remove(is->debugfs_reg[i].dentry); + debugfs_remove(root); +err_root: + is->debugfs_root = NULL; + printk(KERN_ERR "%s: failed to initialize debugfs\n", + is->cam.name); +} + +static void tm13m3_cleanup_debugfs(struct tm13m3 *is) +{ + unsigned int i; + + if (is->debugfs_root) { + for (i = 0; i < ARRAY_SIZE(is->debugfs_reg); i++) + debugfs_remove(is->debugfs_reg[i].dentry); + debugfs_remove(is->debugfs_root); + } +} +#else +static inline void tm13m3_init_debugfs(struct tm13m3 *is) +{ + +} + +static inline void tm13m3_cleanup_debugfs(struct tm13m3 *is) +{ + +} +#endif /* CONFIG_DEBUG_FS */ + +static int tm13m3_get_format(struct atmel_isi_camera *cam, + struct atmel_isi_format *fmt) +{ + struct tm13m3 *is = to_tm13m3(cam); + int ret = 0; + + fmt->pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->input_format = ATMEL_ISI_PIXFMT_CbYCrY; + fmt->input_format = is->current_format; + + fmt->pix.width = 320; + fmt->pix.height = 240; + + return ret; +} + +static int tm13m3_set_format(struct atmel_isi_camera *cam, + struct atmel_isi_format *fmt) +{ + struct tm13m3 *is = to_tm13m3(cam); + int ret = 0; + + fmt->pix.colorspace = V4L2_COLORSPACE_SMPTE170M; +/* + switch(fmt->input_format){ + case ATMEL_ISI_PIXFMT_CbYCrY: + is->current_format = ATMEL_ISI_PIXFMT_CbYCrY; + break; + case ATMEL_ISI_PIXFMT_YCbYCr: + is->current_format = ATMEL_ISI_PIXFMT_YCbYCr; + break; + case ATMEL_ISI_PIXFMT_CrYCbY: + is->current_format = ATMEL_ISI_PIXFMT_CrYCbY; + break; + case ATMEL_ISI_PIXFMT_YCrYCb: + is->current_format = ATMEL_ISI_PIXFMT_YCrYCb; + break; + default: + // force a valid format + fmt->input_format = ATMEL_ISI_PIXFMT_CbYCrY; + is->current_format = ATMEL_ISI_PIXFMT_CbYCrY; + pr_debug("%s: Not supported format, forcing default format\n", + cam->name); + break; + } +*/ + fmt->input_format = ATMEL_ISI_PIXFMT_CrYCbY; + is->current_format = ATMEL_ISI_PIXFMT_CrYCbY; + + /* adjust picture width and height */ + if (fmt->pix.width > TM13M3_MAX_WIDTH) + fmt->pix.width = TM13M3_MAX_WIDTH; + if (fmt->pix.height > TM13M3_MAX_HEIGHT) + fmt->pix.height = TM13M3_MAX_HEIGHT; + + //tm13m3_write_16(is, COLUMN_WIDTH, fmt->pix.width); + //tm13m3_write_16(is, ROW_WIDTH, fmt->pix.height); + //tm13m3_write_16(is, HORIZONTAL_ZOOM, fmt->pix.width); + //tm13m3_write_16(is, VERTICAL_ZOOM, fmt->pix.height); + + //tm13m3_write_16(is, HORIZONTAL_OUTPUT_SIZE_B, fmt->pix.width); + //tm13m3_write_16(is, VERTICAL_OUTPUT_SIZE_B, fmt->pix.height); + + /* FIXME Set context output width needed ??*/ + + pr_debug("%s: set_format %ux%u\n", cam->name, + fmt->pix.width, fmt->pix.height); + return ret; +} + +static void tm13m3_reset_soft(struct tm13m3 *is) +{ + tm13m3_write_16(is, 0x0D, 0x0001); + /*FIXME test if toggling is really needed */ + tm13m3_write_16(is, 0x0D, 0x0000); +} + +static void tm13m3_reset_hardware(struct tm13m3 *is) +{ + gpio_set_value(CAM_RESET, 0); + //FIXME : set correct reset interval usleep(); + gpio_set_value(CAM_RESET, 1); +} + +static int tm13m3_start_capture(struct atmel_isi_camera *cam) +{ + struct tm13m3 *is = to_tm13m3(cam); + int ret = 0; + + return ret; +} + +static int tm13m3_stop_capture(struct atmel_isi_camera *cam) +{ + struct tm13m3 *is = to_tm13m3(cam); + int ret = 0; + + + return ret; +} + +static int tm13m3_init_hardware(struct tm13m3 *is) +{ + int chip_id; + + tm13m3_reset_hardware(is); + /* set register page to reset value*/ + is->current_page = 0; + is->current_format = ATMEL_ISI_PIXFMT_CbYCrY; + + pr_debug("tm13m3: Init sensor\n"); + /* Try to identify the camera */ + chip_id = tm13m3_read_16(is, CHIP_VERSION); + if (chip_id < 0) + return -EIO; + + if (chip_id != MT9M112_CHIP_ID) { + printk(KERN_ERR "%s: Unknown chip ID 0x%04x\n", + is->cam.name, chip_id); + return -ENODEV; + } +#if 0 + /* Configure pll for 36,8 MHz with CLKIN = 20MHz + * fout = fclkin * M * 1 /( 2* (N+1) * (P+1)) + */ + /* Set P = 2 */ + tm13m3_write_16(is, PLL_CONTROL_2, 0x0502); + /* M = 22, N = 1*/ + tm13m3_write_16(is, PLL_CONTROL_1, 0x1601); + /* wake up pll*/ + tm13m3_write_16(is, CLOCK_CONTROL, 0x8000); + /* wait until pll has stabilized */ + mdelay(1); + /* set pll as master clock*/ + tm13m3_write_16(is, CLOCK_CONTROL, 0x0000); + +#endif + /* Set semi-auto mode program mode*/ + tm13m3_write_16(is, PROGRAM_CONTROL, 0x0010); + /* set context B read mode */ + tm13m3_write_16(is, READ_MODE_CONTEXT_B, 0x0100); + /* switch to read+resize context B */ + tm13m3_write_16(is, CONTEXT_CONTROL, 0x0408); + /* set ITU-R BT.656 codes */ + tm13m3_write_16(is, OUTPUT_FORMAT_CONTROL_A, 0x0A00); + /* set sensor image size */ + tm13m3_write_16(is, HORIZONTAL_ZOOM, 320); + tm13m3_write_16(is, VERTICAL_ZOOM, 240); + tm13m3_write_16(is, HORIZONTAL_OUTPUT_SIZE_B, 320); + tm13m3_write_16(is, VERTICAL_OUTPUT_SIZE_B, 240); + tm13m3_write_16(is, COLUMN_WIDTH, 320); + tm13m3_write_16(is, ROW_WIDTH, 240); + return 0; +} +static int tm13m3_detect_client(struct i2c_adapter *adapter, + int address, int kind) +{ + struct i2c_client *client; + struct tm13m3 *is; + int ret; + + pr_debug("tm13m3: detecting client on address 0x%x\n", address); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, + (I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE_DATA + | I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_WRITE_WORD_DATA))) + return 0; + + is = kzalloc(sizeof(struct tm13m3), GFP_KERNEL); + if (!is) + return -ENOMEM; + + client = &is->client; + client->addr = address; + client->adapter = adapter; + client->driver = &tm13m3_driver; + strcpy(client->name, "tm13m3"); + + is->cam.name = client->name; + is->cam.hsync_act_low = 0; + is->cam.vsync_act_low = 0; + is->cam.pclk_act_falling = 0; + /* no SAV/EAV sync -> HSYNC and VSYNC used */ + /*is->cam.has_emb_sync = 0;*/ + is->cam.has_emb_sync = 1; + + is->cam.get_format = tm13m3_get_format; + is->cam.set_format = tm13m3_set_format; + is->cam.start_capture = tm13m3_start_capture; + is->cam.stop_capture = tm13m3_stop_capture; + + mutex_init(&is->mutex); + + is->mclk = clk_get(NULL, mclk_name); + if (IS_ERR(is->mclk)) { + ret = PTR_ERR(is->mclk); + goto err_clk; + } + clk_enable(is->mclk); + + ret = i2c_attach_client(client); + if (ret) + goto err_attach; + + i2c_set_clientdata(client, is); + + ret = tm13m3_init_hardware(is); + if (ret) + goto err_init_hw; + + /* We're up and running. Notify the ISI driver */ + ret = atmel_isi_register_camera(&is->cam); + if (ret) + goto err_register; + + printk(KERN_INFO "TM13M3 Image Sensor at %s:0x%02x\n", + adapter->name, address); + + tm13m3_init_debugfs(is); + + return 0; + +err_register: +err_init_hw: +// at76_reset_hardware(is); + i2c_detach_client(client); +err_attach: + clk_disable(is->mclk); + clk_put(is->mclk); +err_clk: + kfree(is); + return ret; +} + +static int tm13m3_attach_adapter(struct i2c_adapter *adapter) +{ + pr_debug("tm13m3: starting probe for adapter %s (%u)\n", + adapter->name, adapter->id); + return i2c_probe(adapter, &addr_data, &tm13m3_detect_client); +} + +static int tm13m3_detach_client(struct i2c_client *client) +{ + struct tm13m3 *is = i2c_get_clientdata(client); + int ret; + + tm13m3_cleanup_debugfs(is); + atmel_isi_unregister_camera(&is->cam); + + tm13m3_reset_hardware(is); + + ret = i2c_detach_client(client); + if (ret) + return ret; + + clk_disable(is->mclk); + clk_put(is->mclk); + kfree(is); + + return 0; +} + +static struct i2c_driver tm13m3_driver = { + .driver = { + .name = "tm13m3", + }, + .id = I2C_DRIVERID_TM13M3, + .attach_adapter = &tm13m3_attach_adapter, + .detach_client = &tm13m3_detach_client, +}; + +static int __init tm13m3_init(void) +{ + /* + * Set up the master clock, if available. If clk_get() fails, + * this hopefully means that the board generates a suitable + * master clock some other way, which is fine by us. + * + * We need to do this before probing the i2c bus, as the + * camera won't ack any messages when it doesn't have a clock. + */ + mclk_parent = clk_get(NULL, mclk_parent_name); + if (!IS_ERR(mclk_parent)) + clk_enable(mclk_parent); + else { + mclk_parent = NULL; + pr_debug("tm13m3: No parent clock available\n"); + } + + mclk = clk_get(NULL, mclk_name); + if (!IS_ERR(mclk)) { + if (mclk_parent) + clk_set_parent(mclk, mclk_parent); + + clk_set_rate(mclk, 27000000); + clk_enable(mclk); + } else { + mclk = NULL; + pr_debug("tm13m3: No clock set\n"); + } + + gpio_direction_output(CAM_STANDBY, 0); + /* Reset sequence */ + gpio_direction_output(CAM_RESET, 0); + udelay(4); + gpio_set_value(CAM_RESET, 1); + + return i2c_add_driver(&tm13m3_driver); + +} +module_init(tm13m3_init); + +static void __exit tm13m3_exit(void) +{ + if (mclk) { + clk_disable(mclk); + clk_put(mclk); + } + if (mclk_parent) { + clk_disable(mclk_parent); + clk_put(mclk_parent); + } + i2c_del_driver(&tm13m3_driver); +} +module_exit(tm13m3_exit); + +MODULE_DESCRIPTION("Atmel Image Sensor Interface Driver"); +MODULE_AUTHOR("Lars Häring "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index b690148..a3a78cf 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -119,6 +119,7 @@ #define I2C_DRIVERID_WM8750 90 /* Wolfson WM8750 audio codec */ #define I2C_DRIVERID_WM8753 91 /* Wolfson WM8753 audio codec */ #define I2C_DRIVERID_LM4857 92 /* LM4857 Audio Amplifier */ +#define I2C_DRIVERID_TM13M3 93 /* Micron MT9M112 camera */ #define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ -- 1.5.4.4