forked from Minki/linux
8a9947552d
Just about every i2c chip driver sets the I2C_DF_NOTIFY flag, so we can simply make it the default and drop the flag. If any driver really doesn't want to be notified when i2c adapters are added, that driver can simply omit to set .attach_adapter. This approach is also more robust as it prevents accidental NULL pointer dereferences. Signed-off-by: Jean Delvare <khali@linux-fr.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
474 lines
11 KiB
C
474 lines
11 KiB
C
/*
|
|
* indycam.c - Silicon Graphics IndyCam digital camera driver
|
|
*
|
|
* Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org>
|
|
* Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi>
|
|
*
|
|
* 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/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/major.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/videodev.h>
|
|
/* IndyCam decodes stream of photons into digital image representation ;-) */
|
|
#include <linux/video_decoder.h>
|
|
#include <linux/i2c.h>
|
|
|
|
#include "indycam.h"
|
|
|
|
#define INDYCAM_MODULE_VERSION "0.0.5"
|
|
|
|
MODULE_DESCRIPTION("SGI IndyCam driver");
|
|
MODULE_VERSION(INDYCAM_MODULE_VERSION);
|
|
MODULE_AUTHOR("Mikael Nousiainen <tmnousia@cc.hut.fi>");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
// #define INDYCAM_DEBUG
|
|
|
|
#ifdef INDYCAM_DEBUG
|
|
#define dprintk(x...) printk("IndyCam: " x);
|
|
#define indycam_regdump(client) indycam_regdump_debug(client)
|
|
#else
|
|
#define dprintk(x...)
|
|
#define indycam_regdump(client)
|
|
#endif
|
|
|
|
struct indycam {
|
|
struct i2c_client *client;
|
|
u8 version;
|
|
};
|
|
|
|
static struct i2c_driver i2c_driver_indycam;
|
|
|
|
static const u8 initseq[] = {
|
|
INDYCAM_CONTROL_AGCENA, /* INDYCAM_CONTROL */
|
|
INDYCAM_SHUTTER_60, /* INDYCAM_SHUTTER */
|
|
INDYCAM_GAIN_DEFAULT, /* INDYCAM_GAIN */
|
|
0x00, /* INDYCAM_BRIGHTNESS (read-only) */
|
|
INDYCAM_RED_BALANCE_DEFAULT, /* INDYCAM_RED_BALANCE */
|
|
INDYCAM_BLUE_BALANCE_DEFAULT, /* INDYCAM_BLUE_BALANCE */
|
|
INDYCAM_RED_SATURATION_DEFAULT, /* INDYCAM_RED_SATURATION */
|
|
INDYCAM_BLUE_SATURATION_DEFAULT,/* INDYCAM_BLUE_SATURATION */
|
|
};
|
|
|
|
/* IndyCam register handling */
|
|
|
|
static int indycam_read_reg(struct i2c_client *client, u8 reg, u8 *value)
|
|
{
|
|
int ret;
|
|
|
|
if (reg == INDYCAM_REG_RESET) {
|
|
dprintk("indycam_read_reg(): "
|
|
"skipping write-only register %d\n", reg);
|
|
*value = 0;
|
|
return 0;
|
|
}
|
|
|
|
ret = i2c_smbus_read_byte_data(client, reg);
|
|
|
|
if (ret < 0) {
|
|
printk(KERN_ERR "IndyCam: indycam_read_reg(): read failed, "
|
|
"register = 0x%02x\n", reg);
|
|
return ret;
|
|
}
|
|
|
|
*value = (u8)ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int indycam_write_reg(struct i2c_client *client, u8 reg, u8 value)
|
|
{
|
|
int err;
|
|
|
|
if ((reg == INDYCAM_REG_BRIGHTNESS)
|
|
|| (reg == INDYCAM_REG_VERSION)) {
|
|
dprintk("indycam_write_reg(): "
|
|
"skipping read-only register %d\n", reg);
|
|
return 0;
|
|
}
|
|
|
|
dprintk("Writing Reg %d = 0x%02x\n", reg, value);
|
|
err = i2c_smbus_write_byte_data(client, reg, value);
|
|
|
|
if (err) {
|
|
printk(KERN_ERR "IndyCam: indycam_write_reg(): write failed, "
|
|
"register = 0x%02x, value = 0x%02x\n", reg, value);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int indycam_write_block(struct i2c_client *client, u8 reg,
|
|
u8 length, u8 *data)
|
|
{
|
|
int i, err;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
err = indycam_write_reg(client, reg + i, data[i]);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Helper functions */
|
|
|
|
#ifdef INDYCAM_DEBUG
|
|
static void indycam_regdump_debug(struct i2c_client *client)
|
|
{
|
|
int i;
|
|
u8 val;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
indycam_read_reg(client, i, &val);
|
|
dprintk("Reg %d = 0x%02x\n", i, val);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int indycam_get_control(struct i2c_client *client,
|
|
struct indycam_control *ctrl)
|
|
{
|
|
struct indycam *camera = i2c_get_clientdata(client);
|
|
u8 reg;
|
|
int ret = 0;
|
|
|
|
switch (ctrl->type) {
|
|
case INDYCAM_CONTROL_AGC:
|
|
case INDYCAM_CONTROL_AWB:
|
|
ret = indycam_read_reg(client, INDYCAM_REG_CONTROL, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
if (ctrl->type == INDYCAM_CONTROL_AGC)
|
|
ctrl->value = (reg & INDYCAM_CONTROL_AGCENA)
|
|
? 1 : 0;
|
|
else
|
|
ctrl->value = (reg & INDYCAM_CONTROL_AWBCTL)
|
|
? 1 : 0;
|
|
break;
|
|
case INDYCAM_CONTROL_SHUTTER:
|
|
ret = indycam_read_reg(client, INDYCAM_REG_SHUTTER, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = ((s32)reg == 0x00) ? 0xff : ((s32)reg - 1);
|
|
break;
|
|
case INDYCAM_CONTROL_GAIN:
|
|
ret = indycam_read_reg(client, INDYCAM_REG_GAIN, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = (s32)reg;
|
|
break;
|
|
case INDYCAM_CONTROL_RED_BALANCE:
|
|
ret = indycam_read_reg(client, INDYCAM_REG_RED_BALANCE, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = (s32)reg;
|
|
break;
|
|
case INDYCAM_CONTROL_BLUE_BALANCE:
|
|
ret = indycam_read_reg(client, INDYCAM_REG_BLUE_BALANCE, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = (s32)reg;
|
|
break;
|
|
case INDYCAM_CONTROL_RED_SATURATION:
|
|
ret = indycam_read_reg(client,
|
|
INDYCAM_REG_RED_SATURATION, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = (s32)reg;
|
|
break;
|
|
case INDYCAM_CONTROL_BLUE_SATURATION:
|
|
ret = indycam_read_reg(client,
|
|
INDYCAM_REG_BLUE_SATURATION, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = (s32)reg;
|
|
break;
|
|
case INDYCAM_CONTROL_GAMMA:
|
|
if (camera->version == CAMERA_VERSION_MOOSE) {
|
|
ret = indycam_read_reg(client,
|
|
INDYCAM_REG_GAMMA, ®);
|
|
if (ret)
|
|
return -EIO;
|
|
ctrl->value = (s32)reg;
|
|
} else {
|
|
ctrl->value = INDYCAM_GAMMA_DEFAULT;
|
|
}
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int indycam_set_control(struct i2c_client *client,
|
|
struct indycam_control *ctrl)
|
|
{
|
|
struct indycam *camera = i2c_get_clientdata(client);
|
|
u8 reg;
|
|
int ret = 0;
|
|
|
|
switch (ctrl->type) {
|
|
case INDYCAM_CONTROL_AGC:
|
|
case INDYCAM_CONTROL_AWB:
|
|
ret = indycam_read_reg(client, INDYCAM_REG_CONTROL, ®);
|
|
if (ret)
|
|
break;
|
|
|
|
if (ctrl->type == INDYCAM_CONTROL_AGC) {
|
|
if (ctrl->value)
|
|
reg |= INDYCAM_CONTROL_AGCENA;
|
|
else
|
|
reg &= ~INDYCAM_CONTROL_AGCENA;
|
|
} else {
|
|
if (ctrl->value)
|
|
reg |= INDYCAM_CONTROL_AWBCTL;
|
|
else
|
|
reg &= ~INDYCAM_CONTROL_AWBCTL;
|
|
}
|
|
|
|
ret = indycam_write_reg(client, INDYCAM_REG_CONTROL, reg);
|
|
break;
|
|
case INDYCAM_CONTROL_SHUTTER:
|
|
reg = (ctrl->value == 0xff) ? 0x00 : (ctrl->value + 1);
|
|
ret = indycam_write_reg(client, INDYCAM_REG_SHUTTER, reg);
|
|
break;
|
|
case INDYCAM_CONTROL_GAIN:
|
|
ret = indycam_write_reg(client, INDYCAM_REG_GAIN, ctrl->value);
|
|
break;
|
|
case INDYCAM_CONTROL_RED_BALANCE:
|
|
ret = indycam_write_reg(client, INDYCAM_REG_RED_BALANCE,
|
|
ctrl->value);
|
|
break;
|
|
case INDYCAM_CONTROL_BLUE_BALANCE:
|
|
ret = indycam_write_reg(client, INDYCAM_REG_BLUE_BALANCE,
|
|
ctrl->value);
|
|
break;
|
|
case INDYCAM_CONTROL_RED_SATURATION:
|
|
ret = indycam_write_reg(client, INDYCAM_REG_RED_SATURATION,
|
|
ctrl->value);
|
|
break;
|
|
case INDYCAM_CONTROL_BLUE_SATURATION:
|
|
ret = indycam_write_reg(client, INDYCAM_REG_BLUE_SATURATION,
|
|
ctrl->value);
|
|
break;
|
|
case INDYCAM_CONTROL_GAMMA:
|
|
if (camera->version == CAMERA_VERSION_MOOSE) {
|
|
ret = indycam_write_reg(client, INDYCAM_REG_GAMMA,
|
|
ctrl->value);
|
|
}
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* I2C-interface */
|
|
|
|
static int indycam_attach(struct i2c_adapter *adap, int addr, int kind)
|
|
{
|
|
int err = 0;
|
|
struct indycam *camera;
|
|
struct i2c_client *client;
|
|
|
|
printk(KERN_INFO "SGI IndyCam driver version %s\n",
|
|
INDYCAM_MODULE_VERSION);
|
|
|
|
client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
|
|
if (!client)
|
|
return -ENOMEM;
|
|
camera = kmalloc(sizeof(struct indycam), GFP_KERNEL);
|
|
if (!camera) {
|
|
err = -ENOMEM;
|
|
goto out_free_client;
|
|
}
|
|
|
|
memset(client, 0, sizeof(struct i2c_client));
|
|
memset(camera, 0, sizeof(struct indycam));
|
|
|
|
client->addr = addr;
|
|
client->adapter = adap;
|
|
client->driver = &i2c_driver_indycam;
|
|
client->flags = 0;
|
|
strcpy(client->name, "IndyCam client");
|
|
i2c_set_clientdata(client, camera);
|
|
|
|
camera->client = client;
|
|
|
|
err = i2c_attach_client(client);
|
|
if (err)
|
|
goto out_free_camera;
|
|
|
|
camera->version = i2c_smbus_read_byte_data(client,
|
|
INDYCAM_REG_VERSION);
|
|
if (camera->version != CAMERA_VERSION_INDY &&
|
|
camera->version != CAMERA_VERSION_MOOSE) {
|
|
err = -ENODEV;
|
|
goto out_detach_client;
|
|
}
|
|
printk(KERN_INFO "IndyCam v%d.%d detected\n",
|
|
INDYCAM_VERSION_MAJOR(camera->version),
|
|
INDYCAM_VERSION_MINOR(camera->version));
|
|
|
|
indycam_regdump(client);
|
|
|
|
// initialize
|
|
err = indycam_write_block(client, 0, sizeof(initseq), (u8 *)&initseq);
|
|
if (err) {
|
|
printk(KERN_ERR "IndyCam initalization failed\n");
|
|
err = -EIO;
|
|
goto out_detach_client;
|
|
}
|
|
|
|
indycam_regdump(client);
|
|
|
|
// white balance
|
|
err = indycam_write_reg(client, INDYCAM_REG_CONTROL,
|
|
INDYCAM_CONTROL_AGCENA | INDYCAM_CONTROL_AWBCTL);
|
|
if (err) {
|
|
printk(KERN_ERR "IndyCam: White balancing camera failed\n");
|
|
err = -EIO;
|
|
goto out_detach_client;
|
|
}
|
|
|
|
indycam_regdump(client);
|
|
|
|
printk(KERN_INFO "IndyCam initialized\n");
|
|
|
|
return 0;
|
|
|
|
out_detach_client:
|
|
i2c_detach_client(client);
|
|
out_free_camera:
|
|
kfree(camera);
|
|
out_free_client:
|
|
kfree(client);
|
|
return err;
|
|
}
|
|
|
|
static int indycam_probe(struct i2c_adapter *adap)
|
|
{
|
|
/* Indy specific crap */
|
|
if (adap->id == I2C_HW_SGI_VINO)
|
|
return indycam_attach(adap, INDYCAM_ADDR, 0);
|
|
/* Feel free to add probe here :-) */
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int indycam_detach(struct i2c_client *client)
|
|
{
|
|
struct indycam *camera = i2c_get_clientdata(client);
|
|
|
|
i2c_detach_client(client);
|
|
kfree(camera);
|
|
kfree(client);
|
|
return 0;
|
|
}
|
|
|
|
static int indycam_command(struct i2c_client *client, unsigned int cmd,
|
|
void *arg)
|
|
{
|
|
// struct indycam *camera = i2c_get_clientdata(client);
|
|
|
|
/* The old video_decoder interface just isn't enough,
|
|
* so we'll use some custom commands. */
|
|
switch (cmd) {
|
|
case DECODER_GET_CAPABILITIES: {
|
|
struct video_decoder_capability *cap = arg;
|
|
|
|
cap->flags = VIDEO_DECODER_NTSC;
|
|
cap->inputs = 1;
|
|
cap->outputs = 1;
|
|
break;
|
|
}
|
|
case DECODER_GET_STATUS: {
|
|
int *iarg = arg;
|
|
|
|
*iarg = DECODER_STATUS_GOOD | DECODER_STATUS_NTSC |
|
|
DECODER_STATUS_COLOR;
|
|
break;
|
|
}
|
|
case DECODER_SET_NORM: {
|
|
int *iarg = arg;
|
|
|
|
switch (*iarg) {
|
|
case VIDEO_MODE_NTSC:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
}
|
|
case DECODER_SET_INPUT: {
|
|
int *iarg = arg;
|
|
|
|
if (*iarg != 0)
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
case DECODER_SET_OUTPUT: {
|
|
int *iarg = arg;
|
|
|
|
if (*iarg != 0)
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
case DECODER_ENABLE_OUTPUT: {
|
|
/* Always enabled */
|
|
break;
|
|
}
|
|
case DECODER_SET_PICTURE: {
|
|
// struct video_picture *pic = arg;
|
|
/* TODO: convert values for indycam_set_controls() */
|
|
break;
|
|
}
|
|
case DECODER_INDYCAM_GET_CONTROL: {
|
|
return indycam_get_control(client, arg);
|
|
}
|
|
case DECODER_INDYCAM_SET_CONTROL: {
|
|
return indycam_set_control(client, arg);
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct i2c_driver i2c_driver_indycam = {
|
|
.owner = THIS_MODULE,
|
|
.name = "indycam",
|
|
.id = I2C_DRIVERID_INDYCAM,
|
|
.attach_adapter = indycam_probe,
|
|
.detach_client = indycam_detach,
|
|
.command = indycam_command,
|
|
};
|
|
|
|
static int __init indycam_init(void)
|
|
{
|
|
return i2c_add_driver(&i2c_driver_indycam);
|
|
}
|
|
|
|
static void __exit indycam_exit(void)
|
|
{
|
|
i2c_del_driver(&i2c_driver_indycam);
|
|
}
|
|
|
|
module_init(indycam_init);
|
|
module_exit(indycam_exit);
|