/* Micronas vpx 3224D/3225D video processor i2c device driver. Copyright Cherry George Mathew Based on Frodo Looijaard's document on writing i2c clients. See Documentation/i2c/writing-clients. Special thanks to the i2c authors for excellent code templates and useful documentation. */ /* 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. */ /* #include */ #include #include #include #include #include #include #include #include #include #include #include #include "pvcard.h" #include "pvproc.h" /* Module params. */ static int debug; /* Functional level debugging */ #define dprintk(fmt, args...) if (debug>=1) printk(KERN_DEBUG "pvcl-debug: " fmt, ## args); /* Debugging single functions */ #define tprintk(fmt, args...) if (debug>=2) printk(KERN_DEBUG "pvcl-debug: " fmt, ## args); /* Warning - too verbose. Debugging port conversations. */ #define vprintk(fmt, args...) if (debug>=3) printk(KERN_DEBUG "pvcl-debug:" fmt, ## args); /* module parameters: */ const char *client_name = "vpx i2c bus interface"; static int re_entry = 0; /* currently, vmode is the only "sticky" status variable in vpx. * Oh, yes, and vpx_client. */ static __u16 vmode; /* This client pointer contains the address of a detected vpx chip, if any. * Multiple chips, are unlikely on the same adapter, and are ignored. * In such cases, the chip with a lower I2C address is detected. */ static struct i2c_client * vpx_client; int vpx_init_client(struct i2c_client *client); int vpx_attach_adapter(struct i2c_adapter *adapter); int vpx_detach_client(struct i2c_client *client); int vpx_command(struct i2c_client *client, unsigned int cmd, void *arg); void vpx_inc_use(struct i2c_client *client); void vpx_dec_use(struct i2c_client *client); void vpx_poll_fp_busy(struct i2c_client *client); /* TODO: Clearup I2C_DRIVERID_EXP0 issue on the v4l list. */ struct i2c_driver vpx_driver = { name: "Micronas vpx 3224/5d i2c driver", id: I2C_DRIVERID_EXP0, flags: I2C_DF_NOTIFY, attach_adapter: &vpx_attach_adapter, detach_client: &vpx_detach_client, command: &vpx_command, inc_use: &vpx_inc_use, dec_use: &vpx_dec_use }; /* Scan ports 0x43 - 0x47 (7-bit addresses) for the vpx */ static unsigned short normal_i2c[] = { I2C_CLIENT_END }; static unsigned short normal_i2c_range[] = { 0x43, 0x47, I2C_CLIENT_END }; /* Magic definition of all other variables and things */ I2C_CLIENT_INSMOD; /* byte banging routines. i2c-algo-bit.c (lines 363 - 364 assumes data to be little endian. Am I right or have I read the code wrong ? */ s32 vpx_read_byte(struct i2c_client *client, u8 reg) { /* byte-sized register */ return i2c_smbus_read_byte_data(client,reg); } s32 vpx_read_word(struct i2c_client *client, u8 reg) { /* word-sized register */ #ifdef __LITTLE_ENDIAN /* The vpx takes MSB first. OK. topsy turvy! */ return swab16(i2c_smbus_read_word_data(client,reg)); #endif return i2c_smbus_read_word_data(client,reg); } s32 vpx_write_byte(struct i2c_client *client, u8 reg, u16 value) { /* byte-sized register */ return i2c_smbus_write_byte_data(client,reg,value); } s32 vpx_write_word(struct i2c_client *client, u8 reg, u16 value) { /* word-sized register */ /* Do the byte swap */ #ifdef __LITTLE_ENDIAN value=swab16(value); #endif return i2c_smbus_write_word_data(client,reg,value); } s32 vpx_fp_write(struct i2c_client *client, u16 fp_reg, u16 value) { int status; vpx_poll_fp_busy(client); if((status=vpx_write_word(client, FPWR, fp_reg))) return status; vpx_poll_fp_busy(client); return vpx_write_word(client, FPDAT, (value & 0x3fff)); } s32 vpx_fp_read(struct i2c_client *client, u16 fp_reg) { s32 status; vpx_poll_fp_busy(client); if((status=vpx_write_word(client, FPRD, fp_reg))) return status; vpx_poll_fp_busy(client); return (0x3fff & vpx_read_word(client, FPDAT)); } void vpx_poll_fp_busy(struct i2c_client *client) { unsigned long status; status = (unsigned long) vpx_read_byte(client, FPSTA); while(test_bit(2, &status)) { status = (unsigned long) vpx_read_byte(client, FPSTA); /* This is a half hearted attempt to sleep * for 20 milliseconds. The vpx manual says that if * the onboard FP (FP stands for * "Fast Processor" :-O ) is busy, read/write * to it needs to be retried after 20ms. * The priority here is for other processes * to run. Not for us to encourage * couch potatoes... ;) */ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(VPX_BUSY_TIMEOUT); } } /* vpx functional layer */ /* initialize the video processor */ void vpx_pinit(struct i2c_client *client, int model) { int portword; /* I've gone to the trouble of customizing vpx parameters here * with extensibility in mind. For each card, there should be a * special set of settings for the vpx. */ switch(model) { case PVCLPP_COMBO: /* The pvclpp combo card uses a philips TEA5582 * FM demodulator to demodulate sound. * The TEA5582 has a mute control, which is connected * via VPort B, bit 1. Therefore, we have to guess that * the video capture takes place via the 8bits of * VPort A. This is set via FPreg, 0x154. */ portword = vpx_fp_read(client, 0x154) | 0x302; tprintk("Writing %04x to FPreg 0x154 \n.", portword); vpx_fp_write(client, 0x154, portword); /* Set VPort Driver strength. * Got this from the default setting of the pvclpp * application (tvtap) for win98. Booted into * windows, ran tvtap, killed it with softice, * warm booted into linux through int 19, * and read the I2C * registers. */ vpx_write_byte(client, 0xf9, 0x24); vpx_write_byte(client, 0xf8, 0x24); /* VREF pulse width = 6. * Guess how I found out ?? ;-) */ vpx_fp_write(client, 0x153, 0x20); /* Set the input source. There are 3 input * sources, VIN1, VIN2, VIN3. * FPReg(0x21) [1:0] determines * hardcoding to television tuner input * for just now. */ portword = vpx_fp_read(client, 0x21) & 0xfc; vpx_fp_write(client, 0x21, portword | 0x01); } /* Disable Winload table #2. We use Winload table #1 for both fields.*/ vpx_fp_write(client, 0x12B, 0xc00); /* Latch current Values */ portword = vpx_fp_read(client, 0x140); portword |= 0x40; vpx_fp_write(client, 0x140, portword); /* Switch off slicer. */ vpx_write_byte(client, 0xaa, 0x40); tprintk("I2C reg. 0xAA reads %02x. \n", vpx_read_byte(client, 0xaa)); /* Set to PAL - B, G, H, I (50Hz) for testing on my VCR. * Here is the vpx manual listing for setting various standards. * * FPReg(0x20) [2:0] Standard Vert. IF. (MHz) * 0 PAL B,G,H,I 50Hz 4.433618 * 1 NTSC M 60Hz 3.579545 * 2 SECAM 50Hz 4.286 * 3 NTSC44 60Hz 4.433618 * 4 PAL M 60Hz 3.575611 * 5 PAL N 50Hz 3.582056 * 6 PAL 60 60Hz 4.433618 * 7 NTSC COMB 60Hz 3.579545 */ portword = vpx_fp_read(client, 0x20) & 0xff8; vpx_fp_write(client, 0x20, portword | 0x00); /* There's some confusion in the ITU 601 format. Cr and Cb get * Swapped. Let's re-swap them. */ portword = vpx_fp_read(client, 0x126) & 0xeff; vpx_fp_write(client, 0x126, portword | 0x100); } void vpx_start_capture(struct i2c_client *client) { int portword; /* Enable VPortA, VPortB, Pixclk, HREF, VREF, FIELD, VACT, LLC, LLC2 */ portword = vpx_read_byte(client, 0xf2) & 0xf0; portword |= 0x0f; vpx_write_byte(client, 0xf2, portword); } void vpx_stop_capture(struct i2c_client *client) { int portword; /* Disable VPortA, VPortB, Pixclk, HREF, VREF, FIELD, VACT, LLC, LLC2 */ portword = vpx_read_byte(client, 0xf2) & 0xf0; vpx_write_byte(client, 0xf2, portword); } void vpx_set_window(struct i2c_client *client, struct video_window *vwin) { /* Winloadtab1 is loaded here. It is active for both fields. */ int portword; switch(vmode) { case VIDEO_MODE_PAL: { /* Vertical Begin Scanline */ vpx_fp_write(client, 0x120, 20); /* Horizontal Begin, Pixel */ vpx_fp_write(client, 0x123, 5); /* Maximum Lines input 310: PAL is a 625 line s/m. */ vpx_fp_write(client, 0x121, 305); /* Scaled number of Vertical Lines. */ vwin->height = (vwin->height > 305 ? 300 : vwin->height) & 0x1ff; vwin->height = vwin->height < 24 ? 24 : vwin->height; /* Scaled Width of raster line. */ vwin->width = (vwin->width > 800 ? 800 : vwin->width) & 0x7ff; vwin->width = vwin->width < 32 ? 32 : vwin->width; break; } default: return; } /* Update No. of Vlines to vpx */ vpx_fp_write(client, 0x122, vwin->height); /* Width of raster, upto 800 pixels */ vpx_fp_write(client, 0x125, vwin->width); vpx_fp_write(client, 0x124, vwin->width); /* Latch current Values */ portword = vpx_fp_read(client, 0x140); portword |= 0x20; vpx_fp_write(client, 0x140, portword); } void vpx_set_picture(struct i2c_client *client, struct video_picture *vpict) { int portword; tprintk("Brightness: %d, Contrast: %d, Colour: %d, Hue %d \n", vpict->brightness, vpict->contrast, vpict->colour, vpict->hue); /* set contrast FPReg 0x132 [5].[4:0] = contrast ratio */ portword = vpx_fp_read(client, 0x132) & 0xfd0; portword |= (vpict->contrast >> 10) & 0x3f; vpx_fp_write(client, 0x132, portword); tprintk("H/W contrast set to %d.%d \n", vpict->contrast >> 15, vpict->contrast >> 11 & 0x1f); /* Set Brightness */ portword = vpx_fp_read(client, 0x131) & 0xf00; portword |= vpict->brightness >> 8; vpx_fp_write(client, 0x131, portword); } int vpx_detect_client(struct i2c_adapter *adapter, int address, unsigned short flags, int kind) { int err = 0; struct i2c_client *new_client; dprintk("in vpx_detect_client() \n"); if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_EMUL)){ return 0; } if(! (new_client = kmalloc(sizeof(struct i2c_client),GFP_KERNEL))) { return -ENOMEM; } new_client->addr = address; new_client->adapter = adapter; new_client->driver = &vpx_driver; new_client->flags = 0; /* Now, we do the remaining detection. No `force' parameter is used. */ if (vpx_read_byte(new_client, VPX_REG_JEDEC) != VPX_JEDEC_ID) { goto BAILOUT; } if (vpx_read_byte(new_client, VPX_REG_PARTNUM1) != VPX_PARTNUM1_ID) { goto BAILOUT; } if ((vpx_read_byte(new_client, VPX_REG_PARTNUM0) == VPX_3224D_ID)){ printk( KERN_INFO "vpx: Found: vpx 3224d chip @ 0x%02x \n", (new_client->addr << 1) ); if((err=vpx_init_client(new_client))) goto BAILOUT; return 0; } if (vpx_read_byte(new_client, VPX_REG_PARTNUM0) == VPX_3225D_ID){ printk( KERN_INFO "vpx: Found: vpx 3225d chip @ 0x%02x \n", (new_client->addr << 1)); if((err=vpx_init_client(new_client))) goto BAILOUT; return 0; } else{ printk( KERN_WARNING "vpx: Can't find supported vpx chip on this adapter. Sorry.\n"); goto BAILOUT; } BAILOUT: kfree(new_client); return err; } int vpx_init_client(struct i2c_client *client) { int err=0; /* This driver is non re-entrant; ie; can only support one chip at a time. */ if(re_entry) { printk(KERN_INFO "Sorry, only one client allowed per adapter. \n"); return -EBUSY; } re_entry = 1; /* Fill in the remaining client fields. */ strcpy(client->name, client_name); /* Tell the i2c layer a new client has arrived */ if ((err = i2c_attach_client(client))) { return err; } /* Finally link this one client with our global client pointer */ vpx_client = client; return 0; } int vpx_detach_client(struct i2c_client *client) { int err; /* Try to detach the client from i2c space */ if ((err = i2c_detach_client(client))) { printk("vpx322xd: Client deregistration failed, client not detached.\n"); return err; } re_entry=0; MOD_DEC_USE_COUNT; /* Frees client data too, if allocated at the same time */ kfree(client); return 0; } int vpx_command(struct i2c_client *client, unsigned int cmd, void *arg) { /* Please note that it is the caller's responsibility to * maintain state information about the video processor. * The only VPROC_GET command is * used to query the video standard. ie; PAL, NTSC and such. */ switch(cmd) { case VPROC_INIT: { int model = * ((int *) arg); vpx_pinit(client, model); dprintk("VPROC_INIT called. \n"); break; } case VPROC_START_CAPTURE: vpx_start_capture(client); dprintk("VPROC_START_CAPTURE called. \n"); break; case VPROC_STOP_CAPTURE: vpx_stop_capture(client); dprintk("VPROC_STOP_CAPTURE called. \n"); break; case VPROC_SET_CAP_MODE: /* SET to PAL/NTSC/SECAM via struct */ vmode = *((int *)(arg)); dprintk("VPROC_SET_CAP_MODE called. \n"); break; case VPROC_GET_CAP_MODE: memcpy(arg, &vmode, sizeof(int)); break; case VPROC_SET_WINDOW: { struct video_window * vwin = arg; vpx_set_window(client, vwin); dprintk("VPROC_SET_WINDOW called. \n"); break; } case VPROC_SET_PICTURE: { struct video_picture * vpict = arg; vpx_set_picture(client, vpict); dprintk("VPROC_SET_PICTURE called. \n"); break; } } return 0; } int vpx_attach_adapter(struct i2c_adapter *adapter) { int retval; switch ((adapter->id & I2C_ALGO_BIT)) { case I2C_ALGO_BIT: printk(KERN_INFO "vpx: probing i2c adapter %s ... \n", adapter->name); retval = i2c_probe(adapter,&addr_data,&vpx_detect_client); break; default: printk("vpx: skipping adapter %s, adapter type not supported.\n", adapter->name); retval = 0; } /* Had to put this here because of messy code in vpx_detect_client() * TODO: Cleanup the chip detect code in vpx_detect_client(). */ if(!retval) MOD_INC_USE_COUNT; return retval; } void vpx_inc_use(struct i2c_client *client) { MOD_INC_USE_COUNT; } void vpx_dec_use(struct i2c_client *client) { MOD_DEC_USE_COUNT; } int __init vpx_init(void) { int res; printk("vpx version %s (%s)\n", VPX_VERSION, VPX_DATE); if ((res = i2c_add_driver(&vpx_driver))) { printk("vpx: Driver registration failed, module not inserted.\n"); return res; } return 0; } int __init vpx_cleanup(void) { int res; if ((res = i2c_del_driver(&vpx_driver))) { printk("vpx: Driver de-registration failed, module not removed.\n"); return res; } return 0; } EXPORT_NO_SYMBOLS; MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "debug level - 0 off; 1 normal; 2 verbose; "); #ifdef MODULE MODULE_AUTHOR("Cherry George Mathew "); MODULE_DESCRIPTION("i2c driver for the micronas vpx322xd video processor family"); MODULE_LICENSE("GPL"); int init_module(void) { return vpx_init(); } int cleanup_module(void) { return vpx_cleanup(); } #endif /* MODULE */ /* * Overrides for Emacs so that we follow Linus's tabbing style. * --------------------------------------------------------------------------- * Local variables: * c-basic-offset: 8 * End: */