/* * OmniVision OV7610/OV7110 Camera Chip Support Code * * Copyright (c) 1999-2002 Mark McClelland * http://alpha.dyndns.org/ov511/ * * 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; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* This file is not a module yet */ #define __NO_VERSION__ #if defined(OUTSIDE_KERNEL) #if !defined(EXPORT_SYMTAB) #define EXPORT_SYMTAB #endif #include #if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS) #define MODVERSIONS #endif #include #ifdef MODVERSIONS #include #endif #else #include #include #endif #include #include "ov511.h" /* OV7610 registers */ #define REG_GAIN 0x00 /* gain setting (5:0) */ #define REG_BLUE 0x01 /* blue channel balance */ #define REG_RED 0x02 /* red channel balance */ #define REG_SAT 0x03 /* saturation */ /* 04 reserved */ #define REG_CNT 0x05 /* Y contrast */ #define REG_BRT 0x06 /* Y brightness */ /* 08-0b reserved */ #define REG_BLUE_BIAS 0x0C /* blue channel bias (5:0) */ #define REG_RED_BIAS 0x0D /* read channel bias (5:0) */ #define REG_GAMMA_COEFF 0x0E /* gamma settings */ #define REG_WB_RANGE 0x0F /* AEC/ALC/S-AWB settings */ #define REG_EXP 0x10 /* manual exposure setting */ #define REG_CLOCK 0x11 /* polarity/clock prescaler */ #define REG_COM_A 0x12 /* misc common regs */ #define REG_COM_B 0x13 /* misc common regs */ #define REG_COM_C 0x14 /* misc common regs */ #define REG_COM_D 0x15 /* misc common regs */ #define REG_FIELD_DIVIDE 0x16 /* field interval/mode settings */ #define REG_HWIN_START 0x17 /* horizontal window start */ #define REG_HWIN_END 0x18 /* horizontal window end */ #define REG_VWIN_START 0x19 /* vertical window start */ #define REG_VWIN_END 0x1A /* vertical window end */ #define REG_PIXEL_SHIFT 0x1B /* pixel shift */ #define REG_ID_HIGH 0x1C /* manufacturer ID MSB */ #define REG_ID_LOW 0x1D /* manufacturer ID LSB */ /* 0e-0f reserved */ #define REG_COM_E 0x20 /* misc common regs */ #define REG_YOFFSET 0x21 /* Y channel offset */ #define REG_UOFFSET 0x22 /* U channel offset */ /* 23 reserved */ #define REG_ECW 0x24 /* Exposure white level for AEC */ #define REG_ECB 0x25 /* Exposure black level for AEC */ #define REG_COM_F 0x26 /* misc settings */ #define REG_COM_G 0x27 /* misc settings */ #define REG_COM_H 0x28 /* misc settings */ #define REG_COM_I 0x29 /* misc settings */ #define REG_FRAMERATE_H 0x2A /* frame rate MSB + misc */ #define REG_FRAMERATE_L 0x2B /* frame rate LSB */ #define REG_ALC 0x2C /* Auto Level Control settings */ #define REG_COM_J 0x2D /* misc settings */ #define REG_VOFFSET 0x2E /* V channel offset adjustment */ #define REG_ARRAY_BIAS 0x2F /* Array bias -- don't change */ /* 30-32 reserved */ #define REG_YGAMMA 0x33 /* misc gamma settings (7:6) */ #define REG_BIAS_ADJUST 0x34 /* misc bias settings */ #define REG_COM_L 0x35 /* misc settings */ /* 36-37 reserved */ #define REG_COM_K 0x38 /* misc registers */ extern int debug; /* Exists in ov511.c and ov518.c */ struct ov7x10 { int auto_brt; int auto_exp; int bandfilt; int mirror; }; /* Lawrence Glaister reports: * * Register 0x0f in the 7610 has the following effects: * * 0x85 (AEC method 1): Best overall, good contrast range * 0x45 (AEC method 2): Very overexposed * 0xa5 (spec sheet default): Ok, but the black level is * shifted resulting in loss of contrast * 0x05 (old driver setting): very overexposed, too much * contrast */ static struct ovsensor_regvals regvalsNorm7x10[] = { { 0x10, 0xff }, { 0x16, 0x06 }, { 0x28, 0x24 }, { 0x2b, 0xac }, { 0x12, 0x00 }, { 0x38, 0x81 }, { 0x28, 0x24 }, /* 0c */ { 0x0f, 0x85 }, /* lg's setting */ { 0x15, 0x01 }, { 0x20, 0x1c }, { 0x23, 0x2a }, { 0x24, 0x10 }, { 0x25, 0x8a }, { 0x26, 0xa2 }, { 0x27, 0xc2 }, { 0x2a, 0x04 }, { 0x2c, 0xfe }, { 0x2d, 0x93 }, { 0x30, 0x71 }, { 0x31, 0x60 }, { 0x32, 0x26 }, { 0x33, 0x20 }, { 0x34, 0x48 }, { 0x12, 0x24 }, { 0x11, 0x01 }, { 0x0c, 0x24 }, { 0x0d, 0x24 }, { 0xff, 0xff }, /* END MARKER */ }; /* This initializes the OV7x10 sensor and relevant variables. */ static int ov7x10_configure(struct usb_ov511 *ov) { struct ov7x10 *s; int rc; PDEBUG(4, "entered"); ov->spriv = s = kmalloc(sizeof *s, GFP_KERNEL); if (!s) return -ENOMEM; memset(s, 0, sizeof *s); s->auto_brt = 1; s->auto_exp = 1; rc = ov_write_regvals(&ov->internal_client, regvalsNorm7x10); return rc; } static int ov7x10_unconfigure(struct usb_ov511 *ov) { kfree(ov->spriv); return 0; } static int ov7x10_set_control(struct usb_ov511 *ov, struct ovsensor_control *ctl) { struct ov7x10 *s = ov->spriv; struct i2c_client *c= &ov->internal_client; int rc; int v = ctl->value; switch (ctl->id) { case OVSENSOR_CID_CONT: rc = ov_write(c, REG_CNT, v >> 8); break; case OVSENSOR_CID_BRIGHT: rc = ov_write(c, REG_BRT, v >> 8); break; case OVSENSOR_CID_SAT: rc = ov_write(c, REG_SAT, v >> 8); break; case OVSENSOR_CID_HUE: rc = ov_write(c, REG_RED, 0xFF - (v >> 8)); if (rc < 0) goto out; rc = ov_write(c, REG_BLUE, v >> 8); break; case OVSENSOR_CID_EXP: rc = ov_write(c, REG_EXP, v); break; case OVSENSOR_CID_FREQ: { int sixty = (v == 60); rc = ov_write_mask(c, 0x2a, sixty?0x00:0x80, 0x80); if (rc < 0) goto out; rc = ov_write(c, 0x2b, sixty?0x00:0xac); if (rc < 0) goto out; rc = ov_write_mask(c, 0x13, 0x10, 0x10); if (rc < 0) goto out; rc = ov_write_mask(c, 0x13, 0x00, 0x10); break; } case OVSENSOR_CID_BANDFILT: rc = ov_write_mask(c, 0x2d, v?0x04:0x00, 0x04); s->bandfilt = v; break; case OVSENSOR_CID_AUTOBRIGHT: rc = ov_write_mask(c, 0x2d, v?0x10:0x00, 0x10); s->auto_brt = v; break; case OVSENSOR_CID_AUTOEXP: rc = ov_write_mask(c, 0x29, v?0x00:0x80, 0x80); s->auto_exp = v; break; case OVSENSOR_CID_MIRROR: rc = ov_write_mask(c, 0x12, v?0x40:0x00, 0x40); s->mirror = v; break; default: PDEBUG(2, "control not supported: %d", ctl->id); return -EPERM; } out: PDEBUG(3, "id=%d, arg=%d, rc=%d", ctl->id, v, rc); return rc; } static int ov7x10_get_control(struct usb_ov511 *ov, struct ovsensor_control *ctl) { struct ov7x10 *s = ov->spriv; struct i2c_client *c= &ov->internal_client; int rc = 0; unsigned char val = 0; switch (ctl->id) { case OVSENSOR_CID_CONT: rc = ov_read(c, REG_CNT, &val); ctl->value = val << 8; break; case OVSENSOR_CID_BRIGHT: rc = ov_read(c, REG_BRT, &val); ctl->value = val << 8; break; case OVSENSOR_CID_SAT: rc = ov_read(c, REG_SAT, &val); ctl->value = val << 8; break; case OVSENSOR_CID_HUE: rc = ov_read(c, REG_BLUE, &val); ctl->value = val << 8; break; case OVSENSOR_CID_EXP: rc = ov_read(c, REG_EXP, &val); ctl->value = val; break; case OVSENSOR_CID_BANDFILT: ctl->value = s->bandfilt; break; case OVSENSOR_CID_AUTOBRIGHT: ctl->value = s->auto_brt; break; case OVSENSOR_CID_AUTOEXP: ctl->value = s->auto_exp; break; case OVSENSOR_CID_MIRROR: ctl->value = s->mirror; break; default: PDEBUG(2, "control not supported: %d", ctl->id); return -EPERM; } PDEBUG(3, "id=%d, arg=%d, rc=%d", ctl->id, ctl->value, rc); return rc; } static int ov7x10_mode_init(struct usb_ov511 *ov, struct ovsensor_window *win, int qvga) { struct i2c_client *c= &ov->internal_client; int clock; /******** QVGA-specific regs ********/ ov_write(c, 0x14, qvga?0x24:0x04); // FIXME: Does this improve the image quality or frame rate? #if 0 ov_write_mask(c, 0x28, qvga?0x00:0x20, 0x20); ov_write(c, 0x24, 0x10); ov_write(c, 0x25, qvga?0x40:0x8a); ov_write(c, 0x2f, qvga?0x30:0xb0); ov_write(c, 0x35, qvga?0x1c:0x9c); #endif /******** Palette-specific regs ********/ if (win->format == VIDEO_PALETTE_GREY) { ov_write_mask(c, 0x0e, 0x40, 0x40); ov_write_mask(c, 0x13, 0x20, 0x20); } else { ov_write_mask(c, 0x0e, 0x00, 0x40); ov_write_mask(c, 0x13, 0x00, 0x20); } /******** Clock programming ********/ if (ov->compress) { clock = 1; /* This ensures the highest frame rate */ } else if (win->clockdiv == -1) { /* If user didn't override it */ /* Calculate and set the clock divisor */ clock = ((win->sub_flag ? ov->subw * ov->subh : win->width * win->height) * (win->format == VIDEO_PALETTE_GREY ? 2 : 3) / 2) / 66000; } else { clock = win->clockdiv; } PDEBUG(4, "Setting clock divisor to %d", clock); ov_write(c, 0x11, clock); /******** Resolution-specific ********/ if (win->width == 640 && win->height == 480) ov_write(c, 0x35, 0x9e); else ov_write(c, 0x35, 0x1e); return 0; } static int ov7x10_set_window(struct usb_ov511 *ov, struct ovsensor_window *win) { struct i2c_client *c= &ov->internal_client; int ret; int hwsbase, hwebase, vwsbase, vwebase, hwsize, vwsize; int hoffset, voffset, hwscale = 0, vwscale = 0; hwsbase = 0x38; hwebase = 0x3a; vwsbase = vwebase = 0x05; if (win->width > 320 && win->height > 240) { /* VGA */ ret = ov7x10_mode_init(ov, win, 0); if (ret < 0) return ret; hwscale = 2; vwscale = 1; hwsize = 640; vwsize = 480; } else if (win->width > 320 || win->height > 240) { err("Illegal dimensions"); return -EINVAL; } else { /* QVGA */ ret = ov7x10_mode_init(ov, win, 1); if (ret < 0) return ret; hwscale = 1; hwsize = 320; vwsize = 240; } /* Center the window */ hoffset = ((hwsize - win->width) / 2) >> hwscale; voffset = ((vwsize - win->height) / 2) >> vwscale; /* FIXME! - This needs to be changed to support 160x120 and 6620!!! */ if (win->sub_flag) { ov_write(c, 0x17, hwsbase+(ov->subx>>hwscale)); ov_write(c, 0x18, hwebase+((ov->subx+ov->subw)>>hwscale)); ov_write(c, 0x19, vwsbase+(ov->suby>>vwscale)); ov_write(c, 0x1a, vwebase+((ov->suby+ov->subh)>>vwscale)); } else { ov_write(c, 0x17, hwsbase + hoffset); ov_write(c, 0x18, hwebase + hoffset + (hwsize>>hwscale)); ov_write(c, 0x19, vwsbase + voffset); ov_write(c, 0x1a, vwebase + voffset + (vwsize>>vwscale)); } return 0; } static int ov7x10_command(struct usb_ov511 *ov, unsigned int cmd, void *arg) { switch (cmd) { case OVSENSOR_CMD_S_CTRL: return ov7x10_set_control(ov, arg); case OVSENSOR_CMD_G_CTRL: return ov7x10_get_control(ov, arg); case OVSENSOR_CMD_S_MODE: return ov7x10_set_window(ov, arg); default: PDEBUG(2, "command not supported: %d", cmd); return -ENOIOCTLCMD; } } struct ovsensor_ops ov7x10_ops = { .configure = ov7x10_configure, .unconfigure = ov7x10_unconfigure, .command = ov7x10_command, };