#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/i2c.h>
-#include <linux/videodev.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <linux/kthread.h>
#include <asm/semaphore.h>
#include <asm/pgtable.h>
+#include <linux/videodev.h>
#include <media/audiochip.h>
+#include <media/v4l2-common.h>
#include "msp3400.h"
+/* ---------------------------------------------------------------------- */
+
+#define I2C_MSP3400C 0x80
+#define I2C_MSP3400C_ALT 0x88
+
+#define I2C_MSP3400C_DEM 0x10
+#define I2C_MSP3400C_DFP 0x12
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {
+ I2C_MSP3400C >> 1,
+ I2C_MSP3400C_ALT >> 1,
+ I2C_CLIENT_END
+};
+I2C_CLIENT_INSMOD;
+
#define msp3400_dbg(fmt, arg...) \
do { \
if (debug) \
- printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \
+ printk(KERN_INFO "%s debug %d-%04x: " fmt, \
+ client->driver->driver.name, \
i2c_adapter_id(client->adapter), client->addr , ## arg); \
} while (0)
#define msp3400_dbg_mediumvol(fmt, arg...) \
do { \
if (debug >= 2) \
- printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \
+ printk(KERN_INFO "%s debug %d-%04x: " fmt, \
+ client->driver->driver.name, \
i2c_adapter_id(client->adapter), client->addr , ## arg); \
} while (0)
#define msp3400_dbg_highvol(fmt, arg...) \
do { \
if (debug >= 16) \
- printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \
+ printk(KERN_INFO "%s debug %d-%04x: " fmt, \
+ client->driver->driver.name, \
i2c_adapter_id(client->adapter), client->addr , ## arg); \
} while (0)
#define msp3400_err(fmt, arg...) do { \
- printk(KERN_ERR "%s %d-%04x: " fmt, client->driver->name, \
+ printk(KERN_ERR "%s %d-%04x: " fmt, client->driver->driver.name, \
i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0)
#define msp3400_warn(fmt, arg...) do { \
- printk(KERN_WARNING "%s %d-%04x: " fmt, client->driver->name, \
+ printk(KERN_WARNING "%s %d-%04x: " fmt, client->driver->driver.name, \
i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0)
#define msp3400_info(fmt, arg...) do { \
- printk(KERN_INFO "%s %d-%04x: " fmt, client->driver->name, \
+ printk(KERN_INFO "%s %d-%04x: " fmt, client->driver->driver.name, \
i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0)
#define OPMODE_AUTO -1
int rxsubchans;
int muted;
- int left, right; /* volume */
+ int left, right; /* volume */
int bass, treble;
/* shadow register set */
int watch_stereo:1;
};
-#define MIN(a,b) (((a)>(b))?(b):(a))
-#define MAX(a,b) (((a)>(b))?(a):(b))
#define HAVE_NICAM(msp) (((msp->rev2>>8) & 0xff) != 00)
#define HAVE_SIMPLE(msp) ((msp->rev1 & 0xff) >= 'D'-'@')
#define HAVE_SIMPLER(msp) ((msp->rev1 & 0xff) >= 'G'-'@')
MODULE_PARM_DESC(amsound, "Hardwire AM sound at 6.5Hz (France), FM can autoscan");
MODULE_PARM_DESC(dolby, "Activates Dolby processsing");
-/* ---------------------------------------------------------------------- */
-
-#define I2C_MSP3400C 0x80
-#define I2C_MSP3400C_ALT 0x88
-
-#define I2C_MSP3400C_DEM 0x10
-#define I2C_MSP3400C_DFP 0x12
-
-/* Addresses to scan */
-static unsigned short normal_i2c[] = {
- I2C_MSP3400C >> 1,
- I2C_MSP3400C_ALT >> 1,
- I2C_CLIENT_END
-};
-I2C_CLIENT_INSMOD;
MODULE_DESCRIPTION("device driver for msp34xx TV sound processor");
MODULE_AUTHOR("Gerd Knorr");
int vol = 0, val = 0, balance = 0;
if (!muted) {
- /* 0x7f instead if 0x73 here has sound quality issues,
- * probably due to overmodulation + clipping ... */
vol = (left > right) ? left : right;
- val = (vol * 0x73 / 65535) << 8;
+ val = (vol * 0x7f / 65535) << 8;
}
if (vol > 0) {
balance = ((right - left) * 127) / vol;
}
}
-#define MSP3400_MAX 4
-static struct i2c_client *msps[MSP3400_MAX];
static void msp3400c_restore_dfp(struct i2c_client *client)
{
struct msp3400c *msp = i2c_get_clientdata(client);
msp->watch_stereo = 0;
}
+
static int msp3400c_thread(void *data)
{
struct i2c_client *client = data;
struct CARRIER_DETECT *cd;
int count, max1,max2,val1,val2, val,this;
+
msp3400_info("msp3400 daemon started\n");
for (;;) {
msp3400_dbg_mediumvol("msp3400 thread: sleep\n");
int mode,val,i,std;
msp3400_info("msp3410 daemon started\n");
+
for (;;) {
msp3400_dbg_mediumvol("msp3410 thread: sleep\n");
msp34xx_sleep(msp,-1);
int val, std, i;
msp3400_info("msp34xxg daemon started\n");
+
msp->source = 1; /* default */
for (;;) {
msp3400_dbg_mediumvol("msp34xxg thread: sleep\n");
/* ----------------------------------------------------------------------- */
-static int msp_attach(struct i2c_adapter *adap, int addr, int kind);
-static int msp_detach(struct i2c_client *client);
-static int msp_probe(struct i2c_adapter *adap);
-static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg);
-
-static int msp_suspend(struct device * dev, pm_message_t state);
-static int msp_resume(struct device * dev);
-
-static void msp_wake_thread(struct i2c_client *client);
-
-static struct i2c_driver driver = {
- .owner = THIS_MODULE,
- .name = "msp3400",
- .id = I2C_DRIVERID_MSP3400,
- .flags = I2C_DF_NOTIFY,
- .attach_adapter = msp_probe,
- .detach_client = msp_detach,
- .command = msp_command,
- .driver = {
- .suspend = msp_suspend,
- .resume = msp_resume,
- },
-};
-
-static struct i2c_client client_template =
-{
- .name = "(unset)",
- .flags = I2C_CLIENT_ALLOW_USE,
- .driver = &driver,
-};
-
-static int msp_attach(struct i2c_adapter *adap, int addr, int kind)
-{
- struct msp3400c *msp;
- struct i2c_client *client = &client_template;
- int (*thread_func)(void *data) = NULL;
- int i;
-
- client_template.adapter = adap;
- client_template.addr = addr;
-
- if (-1 == msp3400c_reset(&client_template)) {
- msp3400_dbg("no chip found\n");
- return -1;
- }
-
- if (NULL == (client = kmalloc(sizeof(struct i2c_client),GFP_KERNEL)))
- return -ENOMEM;
- memcpy(client,&client_template,sizeof(struct i2c_client));
- if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) {
- kfree(client);
- return -ENOMEM;
- }
-
- memset(msp,0,sizeof(struct msp3400c));
- msp->norm = VIDEO_MODE_NTSC;
- msp->left = 58880; /* 0db gain */
- msp->right = 58880; /* 0db gain */
- msp->bass = 32768;
- msp->treble = 32768;
- msp->input = -1;
- msp->muted = 0;
- msp->i2s_mode = 0;
- for (i = 0; i < DFP_COUNT; i++)
- msp->dfp_regs[i] = -1;
-
- i2c_set_clientdata(client, msp);
- init_waitqueue_head(&msp->wq);
-
- if (-1 == msp3400c_reset(client)) {
- kfree(msp);
- kfree(client);
- msp3400_dbg("no chip found\n");
- return -1;
- }
-
- msp->rev1 = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1e);
- if (-1 != msp->rev1)
- msp->rev2 = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1f);
- if ((-1 == msp->rev1) || (0 == msp->rev1 && 0 == msp->rev2)) {
- kfree(msp);
- kfree(client);
- msp3400_dbg("error while reading chip version\n");
- return -1;
- }
- msp3400_dbg("rev1=0x%04x, rev2=0x%04x\n", msp->rev1, msp->rev2);
-
- msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
-
- snprintf(client->name, sizeof(client->name), "MSP%c4%02d%c-%c%d",
- ((msp->rev1>>4)&0x0f) + '3',
- (msp->rev2>>8)&0xff, (msp->rev1&0x0f)+'@',
- ((msp->rev1>>8)&0xff)+'@', msp->rev2&0x1f);
-
- msp->opmode = opmode;
- if (OPMODE_AUTO == msp->opmode) {
- if (HAVE_SIMPLER(msp))
- msp->opmode = OPMODE_SIMPLER;
- else if (HAVE_SIMPLE(msp))
- msp->opmode = OPMODE_SIMPLE;
- else
- msp->opmode = OPMODE_MANUAL;
- }
-
- /* hello world :-) */
- msp3400_info("chip=%s", client->name);
- if (HAVE_NICAM(msp))
- printk(" +nicam");
- if (HAVE_SIMPLE(msp))
- printk(" +simple");
- if (HAVE_SIMPLER(msp))
- printk(" +simpler");
- if (HAVE_RADIO(msp))
- printk(" +radio");
-
- /* version-specific initialization */
- switch (msp->opmode) {
- case OPMODE_MANUAL:
- printk(" mode=manual");
- thread_func = msp3400c_thread;
- break;
- case OPMODE_SIMPLE:
- printk(" mode=simple");
- thread_func = msp3410d_thread;
- break;
- case OPMODE_SIMPLER:
- printk(" mode=simpler");
- thread_func = msp34xxg_thread;
- break;
- }
- printk("\n");
-
- /* startup control thread if needed */
- if (thread_func) {
- msp->kthread = kthread_run(thread_func, client, "msp34xx");
-
- if (NULL == msp->kthread)
- msp3400_warn("kernel_thread() failed\n");
- msp_wake_thread(client);
- }
-
- /* done */
- i2c_attach_client(client);
-
- /* update our own array */
- for (i = 0; i < MSP3400_MAX; i++) {
- if (NULL == msps[i]) {
- msps[i] = client;
- break;
- }
- }
-
- return 0;
-}
-
-static int msp_detach(struct i2c_client *client)
-{
- struct msp3400c *msp = i2c_get_clientdata(client);
- int i;
-
- /* shutdown control thread */
- if (msp->kthread) {
- msp->restart = 1;
- kthread_stop(msp->kthread);
- }
- msp3400c_reset(client);
-
- /* update our own array */
- for (i = 0; i < MSP3400_MAX; i++) {
- if (client == msps[i]) {
- msps[i] = NULL;
- break;
- }
- }
-
- i2c_detach_client(client);
-
- kfree(msp);
- kfree(client);
- return 0;
-}
-
-static int msp_probe(struct i2c_adapter *adap)
-{
- if (adap->class & I2C_CLASS_TV_ANALOG)
- return i2c_probe(adap, &addr_data, msp_attach);
- return 0;
-}
-
static void msp_wake_thread(struct i2c_client *client)
{
struct msp3400c *msp = i2c_get_clientdata(client);
}
}
+static struct v4l2_queryctrl msp34xx_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 58880,
+ .flags = 0,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ },{
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ .flags = 0,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_BASS,
+ .name = "Bass",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 32768,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ },{
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .name = "Treble",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 32768,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ },
+};
+
+
static void msp_any_set_audmode(struct i2c_client *client, int audmode)
{
struct msp3400c *msp = i2c_get_clientdata(client);
}
}
+static int msp_get_ctrl(struct i2c_client *client, struct v4l2_control *ctrl)
+{
+ struct msp3400c *msp = i2c_get_clientdata(client);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = msp->muted;
+ return 0;
+ case V4L2_CID_AUDIO_BALANCE:
+ {
+ int volume = max(msp->left, msp->right);
+
+ ctrl->value = (32768 * min(msp->left, msp->right)) /
+ (volume ? volume : 1);
+ ctrl->value = (msp->left < msp->right) ?
+ (65535 - ctrl->value) : ctrl->value;
+ if (0 == volume)
+ ctrl->value = 32768;
+ return 0;
+ }
+ case V4L2_CID_AUDIO_BASS:
+ ctrl->value = msp->bass;
+ return 0;
+ case V4L2_CID_AUDIO_TREBLE:
+ ctrl->value = msp->treble;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = max(msp->left, msp->right);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int msp_set_ctrl(struct i2c_client *client, struct v4l2_control *ctrl)
+{
+ struct msp3400c *msp = i2c_get_clientdata(client);
+ int set_volume=0, balance, volume;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value>=0 && ctrl->value<2)
+ msp->muted = ctrl->value;
+ else
+ return -ERANGE;
+
+ msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
+ return 0;
+ case V4L2_CID_AUDIO_BALANCE:
+ balance=ctrl->value;
+ volume = max(msp->left, msp->right);
+ set_volume=1;
+ break;
+ case V4L2_CID_AUDIO_BASS:
+ msp->bass=ctrl->value;
+ msp3400c_setbass(client, msp->bass);
+ return 0;
+ case V4L2_CID_AUDIO_TREBLE:
+ msp->treble=ctrl->value;
+ msp3400c_settreble(client, msp->treble);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ volume = max(msp->left, msp->right);
+
+ balance = (32768 * min(msp->left, msp->right)) /
+ (volume ? volume : 1);
+ balance = (msp->left < msp->right) ?
+ (65535 - balance) : balance;
+ if (0 == volume)
+ balance = 32768;
+
+ volume=ctrl->value;
+ set_volume=1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (set_volume) {
+ msp->left = (min(65536 - balance, 32768) * volume) / 32768;
+ msp->right = (min(balance, 32768) * volume) / 32768;
+
+ msp3400_dbg("volume=%d, balance=%d, left=%d, right=%d",
+ volume,balance,msp->left,msp->right);
+
+ msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
+ }
+ return 0;
+}
static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
if (msp->muted)
va->flags |= VIDEO_AUDIO_MUTE;
- va->volume = MAX(msp->left, msp->right);
- va->balance = (32768 * MIN(msp->left, msp->right)) /
+ va->volume = max(msp->left, msp->right);
+ va->balance = (32768 * min(msp->left, msp->right)) /
(va->volume ? va->volume : 1);
va->balance = (msp->left < msp->right) ?
(65535 - va->balance) : va->balance;
msp3400_dbg("VIDIOCSAUDIO\n");
msp->muted = (va->flags & VIDEO_AUDIO_MUTE);
- msp->left = (MIN(65536 - va->balance, 32768) *
+ msp->left = (min(65536 - va->balance, 32768) *
va->volume) / 32768;
- msp->right = (MIN(va->balance, 32768) * va->volume) / 32768;
+ msp->right = (min((int)va->balance, 32768) * va->volume) / 32768;
msp->bass = va->bass;
msp->treble = va->treble;
msp3400_dbg("VIDIOCSAUDIO setting va->volume to %d\n",
if (a->index<0||a->index>2)
return -EINVAL;
- if (a->index==2) {
- if (a->mode == V4L2_AUDMODE_32BITS)
- msp->i2s_mode=1;
- else
- msp->i2s_mode=0;
- }
- msp3400_dbg("Setting audio out on msp34xx to input %i, mode %i\n",
- a->index,msp->i2s_mode);
+ msp3400_dbg("Setting audio out on msp34xx to input %i\n",a->index);
msp3400c_set_scart(client,msp->in_scart,a->index+1);
break;
}
+ case VIDIOC_INT_I2S_CLOCK_FREQ:
+ {
+ u32 *a=(u32 *)arg;
+
+ msp3400_dbg("Setting I2S speed to %d\n",*a);
+
+ switch (*a) {
+ case 1024000:
+ msp->i2s_mode=0;
+ break;
+ case 2048000:
+ msp->i2s_mode=1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ }
+
+ case VIDIOC_QUERYCTRL:
+ {
+ struct v4l2_queryctrl *qc = arg;
+ int i;
+
+ msp3400_dbg("VIDIOC_QUERYCTRL\n");
+
+ for (i = 0; i < ARRAY_SIZE(msp34xx_qctrl); i++)
+ if (qc->id && qc->id == msp34xx_qctrl[i].id) {
+ memcpy(qc, &(msp34xx_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+
+ return -EINVAL;
+ }
+ case VIDIOC_G_CTRL:
+ {
+ struct v4l2_control *ctrl = arg;
+ msp3400_dbg("VIDIOC_G_CTRL\n");
+
+ return msp_get_ctrl(client, ctrl);
+ }
+ case VIDIOC_S_CTRL:
+ {
+ struct v4l2_control *ctrl = arg;
+
+ msp3400_dbg("VIDIOC_S_CTRL\n");
+
+ return msp_set_ctrl(client, ctrl);
+ }
default:
/* nothing */
{
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
- msp3400_dbg("msp34xx: suspend\n");
+ msp3400_dbg("suspend\n");
msp3400c_reset(client);
return 0;
}
{
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
- msp3400_dbg("msp34xx: resume\n");
+ msp3400_dbg("resume\n");
msp_wake_thread(client);
return 0;
}
/* ----------------------------------------------------------------------- */
+static int msp_probe(struct i2c_adapter *adap);
+static int msp_detach(struct i2c_client *client);
+
+static struct i2c_driver driver = {
+ .owner = THIS_MODULE,
+ .name = "msp3400",
+ .id = I2C_DRIVERID_MSP3400,
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = msp_probe,
+ .detach_client = msp_detach,
+ .command = msp_command,
+ .driver = {
+ .suspend = msp_suspend,
+ .resume = msp_resume,
+ },
+};
+
+static struct i2c_client client_template =
+{
+ .name = "(unset)",
+ .flags = I2C_CLIENT_ALLOW_USE,
+ .driver = &driver,
+};
+
+static int msp_attach(struct i2c_adapter *adap, int addr, int kind)
+{
+ struct msp3400c *msp;
+ struct i2c_client *client = &client_template;
+ int (*thread_func)(void *data) = NULL;
+ int i;
+
+ client_template.adapter = adap;
+ client_template.addr = addr;
+
+ if (-1 == msp3400c_reset(&client_template)) {
+ msp3400_dbg("no chip found\n");
+ return -1;
+ }
+
+ if (NULL == (client = kmalloc(sizeof(struct i2c_client),GFP_KERNEL)))
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) {
+ kfree(client);
+ return -ENOMEM;
+ }
+
+ memset(msp,0,sizeof(struct msp3400c));
+ msp->norm = VIDEO_MODE_NTSC;
+ msp->left = 58880; /* 0db gain */
+ msp->right = 58880; /* 0db gain */
+ msp->bass = 32768;
+ msp->treble = 32768;
+ msp->input = -1;
+ msp->muted = 0;
+ msp->i2s_mode = 0;
+ for (i = 0; i < DFP_COUNT; i++)
+ msp->dfp_regs[i] = -1;
+
+ i2c_set_clientdata(client, msp);
+ init_waitqueue_head(&msp->wq);
+
+ if (-1 == msp3400c_reset(client)) {
+ kfree(msp);
+ kfree(client);
+ msp3400_dbg("no chip found\n");
+ return -1;
+ }
+
+ msp->rev1 = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1e);
+ if (-1 != msp->rev1)
+ msp->rev2 = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1f);
+ if ((-1 == msp->rev1) || (0 == msp->rev1 && 0 == msp->rev2)) {
+ kfree(msp);
+ kfree(client);
+ msp3400_dbg("error while reading chip version\n");
+ return -1;
+ }
+ msp3400_dbg("rev1=0x%04x, rev2=0x%04x\n", msp->rev1, msp->rev2);
+
+ msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
+
+ snprintf(client->name, sizeof(client->name), "MSP%c4%02d%c-%c%d",
+ ((msp->rev1>>4)&0x0f) + '3',
+ (msp->rev2>>8)&0xff, (msp->rev1&0x0f)+'@',
+ ((msp->rev1>>8)&0xff)+'@', msp->rev2&0x1f);
+
+ msp->opmode = opmode;
+ if (OPMODE_AUTO == msp->opmode) {
+ if (HAVE_SIMPLER(msp))
+ msp->opmode = OPMODE_SIMPLER;
+ else if (HAVE_SIMPLE(msp))
+ msp->opmode = OPMODE_SIMPLE;
+ else
+ msp->opmode = OPMODE_MANUAL;
+ }
+
+ /* hello world :-) */
+ msp3400_info("chip=%s", client->name);
+ if (HAVE_NICAM(msp))
+ printk(" +nicam");
+ if (HAVE_SIMPLE(msp))
+ printk(" +simple");
+ if (HAVE_SIMPLER(msp))
+ printk(" +simpler");
+ if (HAVE_RADIO(msp))
+ printk(" +radio");
+
+ /* version-specific initialization */
+ switch (msp->opmode) {
+ case OPMODE_MANUAL:
+ printk(" mode=manual");
+ thread_func = msp3400c_thread;
+ break;
+ case OPMODE_SIMPLE:
+ printk(" mode=simple");
+ thread_func = msp3410d_thread;
+ break;
+ case OPMODE_SIMPLER:
+ printk(" mode=simpler");
+ thread_func = msp34xxg_thread;
+ break;
+ }
+ printk("\n");
+
+ /* startup control thread if needed */
+ if (thread_func) {
+ msp->kthread = kthread_run(thread_func, client, "msp34xx");
+
+ if (NULL == msp->kthread)
+ msp3400_warn("kernel_thread() failed\n");
+ msp_wake_thread(client);
+ }
+
+ /* done */
+ i2c_attach_client(client);
+
+ return 0;
+}
+
+static int msp_detach(struct i2c_client *client)
+{
+ struct msp3400c *msp = i2c_get_clientdata(client);
+
+ /* shutdown control thread */
+ if (msp->kthread) {
+ msp->restart = 1;
+ kthread_stop(msp->kthread);
+ }
+ msp3400c_reset(client);
+
+ i2c_detach_client(client);
+
+ kfree(msp);
+ kfree(client);
+ return 0;
+}
+
+static int msp_probe(struct i2c_adapter *adap)
+{
+ if (adap->class & I2C_CLASS_TV_ANALOG)
+ return i2c_probe(adap, &addr_data, msp_attach);
+ return 0;
+}
+
static int __init msp3400_init_module(void)
{
return i2c_add_driver(&driver);