| /* |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/string.h> |
| #include <linux/timer.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/videodev.h> |
| #include <linux/init.h> |
| #include <linux/kdev_t.h> |
| #include <linux/sound.h> |
| #include <linux/soundcard.h> |
| |
| #include <asm/semaphore.h> |
| #include <asm/uaccess.h> |
| |
| |
| #define DEV_MAX 4 |
| |
| static int devnr = -1; |
| module_param(devnr, int, 0644); |
| |
| MODULE_AUTHOR("Gerd Knorr"); |
| MODULE_LICENSE("GPL"); |
| |
| /* ----------------------------------------------------------------------- */ |
| |
| struct TVMIXER { |
| struct i2c_client *dev; |
| int minor; |
| int count; |
| }; |
| |
| static struct TVMIXER devices[DEV_MAX]; |
| |
| static int tvmixer_adapters(struct i2c_adapter *adap); |
| static int tvmixer_clients(struct i2c_client *client); |
| |
| /* ----------------------------------------------------------------------- */ |
| |
| static int mix_to_v4l(int i) |
| { |
| int r; |
| |
| r = ((i & 0xff) * 65536 + 50) / 100; |
| if (r > 65535) r = 65535; |
| if (r < 0) r = 0; |
| return r; |
| } |
| |
| static int v4l_to_mix(int i) |
| { |
| int r; |
| |
| r = (i * 100 + 32768) / 65536; |
| if (r > 100) r = 100; |
| if (r < 0) r = 0; |
| return r | (r << 8); |
| } |
| |
| static int v4l_to_mix2(int l, int r) |
| { |
| r = (r * 100 + 32768) / 65536; |
| if (r > 100) r = 100; |
| if (r < 0) r = 0; |
| l = (l * 100 + 32768) / 65536; |
| if (l > 100) l = 100; |
| if (l < 0) l = 0; |
| return (r << 8) | l; |
| } |
| |
| static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| struct video_audio va; |
| int left,right,ret,val = 0; |
| struct TVMIXER *mix = file->private_data; |
| struct i2c_client *client = mix->dev; |
| void __user *argp = (void __user *)arg; |
| int __user *p = argp; |
| |
| if (NULL == client) |
| return -ENODEV; |
| |
| if (cmd == SOUND_MIXER_INFO) { |
| mixer_info info; |
| strlcpy(info.id, "tv card", sizeof(info.id)); |
| strlcpy(info.name, client->name, sizeof(info.name)); |
| info.modify_counter = 42 /* FIXME */; |
| if (copy_to_user(argp, &info, sizeof(info))) |
| return -EFAULT; |
| return 0; |
| } |
| if (cmd == SOUND_OLD_MIXER_INFO) { |
| _old_mixer_info info; |
| strlcpy(info.id, "tv card", sizeof(info.id)); |
| strlcpy(info.name, client->name, sizeof(info.name)); |
| if (copy_to_user(argp, &info, sizeof(info))) |
| return -EFAULT; |
| return 0; |
| } |
| if (cmd == OSS_GETVERSION) |
| return put_user(SOUND_VERSION, p); |
| |
| if (_SIOC_DIR(cmd) & _SIOC_WRITE) |
| if (get_user(val, p)) |
| return -EFAULT; |
| |
| /* read state */ |
| memset(&va,0,sizeof(va)); |
| client->driver->command(client,VIDIOCGAUDIO,&va); |
| |
| switch (cmd) { |
| case MIXER_READ(SOUND_MIXER_RECMASK): |
| case MIXER_READ(SOUND_MIXER_CAPS): |
| case MIXER_READ(SOUND_MIXER_RECSRC): |
| case MIXER_WRITE(SOUND_MIXER_RECSRC): |
| ret = 0; |
| break; |
| |
| case MIXER_READ(SOUND_MIXER_STEREODEVS): |
| ret = SOUND_MASK_VOLUME; |
| break; |
| case MIXER_READ(SOUND_MIXER_DEVMASK): |
| ret = SOUND_MASK_VOLUME; |
| if (va.flags & VIDEO_AUDIO_BASS) |
| ret |= SOUND_MASK_BASS; |
| if (va.flags & VIDEO_AUDIO_TREBLE) |
| ret |= SOUND_MASK_TREBLE; |
| break; |
| |
| case MIXER_WRITE(SOUND_MIXER_VOLUME): |
| left = mix_to_v4l(val); |
| right = mix_to_v4l(val >> 8); |
| va.volume = max(left,right); |
| va.balance = (32768*min(left,right)) / (va.volume ? va.volume : 1); |
| va.balance = (left<right) ? (65535-va.balance) : va.balance; |
| if (va.volume) |
| va.flags &= ~VIDEO_AUDIO_MUTE; |
| client->driver->command(client,VIDIOCSAUDIO,&va); |
| client->driver->command(client,VIDIOCGAUDIO,&va); |
| /* fall throuth */ |
| case MIXER_READ(SOUND_MIXER_VOLUME): |
| left = (min(65536 - va.balance,32768) * |
| va.volume) / 32768; |
| right = (min(va.balance,(u16)32768) * |
| va.volume) / 32768; |
| ret = v4l_to_mix2(left,right); |
| break; |
| |
| case MIXER_WRITE(SOUND_MIXER_BASS): |
| va.bass = mix_to_v4l(val); |
| client->driver->command(client,VIDIOCSAUDIO,&va); |
| client->driver->command(client,VIDIOCGAUDIO,&va); |
| /* fall throuth */ |
| case MIXER_READ(SOUND_MIXER_BASS): |
| ret = v4l_to_mix(va.bass); |
| break; |
| |
| case MIXER_WRITE(SOUND_MIXER_TREBLE): |
| va.treble = mix_to_v4l(val); |
| client->driver->command(client,VIDIOCSAUDIO,&va); |
| client->driver->command(client,VIDIOCGAUDIO,&va); |
| /* fall throuth */ |
| case MIXER_READ(SOUND_MIXER_TREBLE): |
| ret = v4l_to_mix(va.treble); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| if (put_user(ret, p)) |
| return -EFAULT; |
| return 0; |
| } |
| |
| static int tvmixer_open(struct inode *inode, struct file *file) |
| { |
| int i, minor = iminor(inode); |
| struct TVMIXER *mix = NULL; |
| struct i2c_client *client = NULL; |
| |
| for (i = 0; i < DEV_MAX; i++) { |
| if (devices[i].minor == minor) { |
| mix = devices+i; |
| client = mix->dev; |
| break; |
| } |
| } |
| |
| if (NULL == client) |
| return -ENODEV; |
| |
| /* lock bttv in memory while the mixer is in use */ |
| file->private_data = mix; |
| if (client->adapter->owner) |
| try_module_get(client->adapter->owner); |
| return 0; |
| } |
| |
| static int tvmixer_release(struct inode *inode, struct file *file) |
| { |
| struct TVMIXER *mix = file->private_data; |
| struct i2c_client *client; |
| |
| client = mix->dev; |
| if (NULL == client) { |
| return -ENODEV; |
| } |
| |
| if (client->adapter->owner) |
| module_put(client->adapter->owner); |
| return 0; |
| } |
| |
| static struct i2c_driver driver = { |
| .driver = { |
| .name = "tvmixer", |
| }, |
| .id = I2C_DRIVERID_TVMIXER, |
| .detach_adapter = tvmixer_adapters, |
| .attach_adapter = tvmixer_adapters, |
| .detach_client = tvmixer_clients, |
| }; |
| |
| static const struct file_operations tvmixer_fops = { |
| .owner = THIS_MODULE, |
| .llseek = no_llseek, |
| .ioctl = tvmixer_ioctl, |
| .open = tvmixer_open, |
| .release = tvmixer_release, |
| }; |
| |
| /* ----------------------------------------------------------------------- */ |
| |
| static int tvmixer_adapters(struct i2c_adapter *adap) |
| { |
| struct list_head *item; |
| struct i2c_client *client; |
| |
| list_for_each(item,&adap->clients) { |
| client = list_entry(item, struct i2c_client, list); |
| tvmixer_clients(client); |
| } |
| return 0; |
| } |
| |
| static int tvmixer_clients(struct i2c_client *client) |
| { |
| struct video_audio va; |
| int i,minor; |
| |
| if (!(client->adapter->class & I2C_CLASS_TV_ANALOG)) |
| return -1; |
| |
| /* unregister ?? */ |
| for (i = 0; i < DEV_MAX; i++) { |
| if (devices[i].dev == client) { |
| /* unregister */ |
| unregister_sound_mixer(devices[i].minor); |
| devices[i].dev = NULL; |
| devices[i].minor = -1; |
| printk("tvmixer: %s unregistered (#1)\n", |
| client->name); |
| return 0; |
| } |
| } |
| |
| /* look for a free slot */ |
| for (i = 0; i < DEV_MAX; i++) |
| if (NULL == devices[i].dev) |
| break; |
| if (i == DEV_MAX) { |
| printk(KERN_WARNING "tvmixer: DEV_MAX too small\n"); |
| return -1; |
| } |
| |
| /* audio chip with mixer ??? */ |
| if (NULL == client->driver->command) |
| return -1; |
| memset(&va,0,sizeof(va)); |
| if (0 != client->driver->command(client,VIDIOCGAUDIO,&va)) |
| return -1; |
| if (0 == (va.flags & VIDEO_AUDIO_VOLUME)) |
| return -1; |
| |
| /* everything is fine, register */ |
| if ((minor = register_sound_mixer(&tvmixer_fops,devnr)) < 0) { |
| printk(KERN_ERR "tvmixer: cannot allocate mixer device\n"); |
| return -1; |
| } |
| |
| devices[i].minor = minor; |
| devices[i].count = 0; |
| devices[i].dev = client; |
| printk("tvmixer: %s (%s) registered with minor %d\n", |
| client->name,client->adapter->name,minor); |
| |
| return 0; |
| } |
| |
| /* ----------------------------------------------------------------------- */ |
| |
| static int __init tvmixer_init_module(void) |
| { |
| int i; |
| |
| for (i = 0; i < DEV_MAX; i++) |
| devices[i].minor = -1; |
| |
| return i2c_add_driver(&driver); |
| } |
| |
| static void __exit tvmixer_cleanup_module(void) |
| { |
| int i; |
| |
| i2c_del_driver(&driver); |
| for (i = 0; i < DEV_MAX; i++) { |
| if (devices[i].minor != -1) { |
| unregister_sound_mixer(devices[i].minor); |
| printk("tvmixer: %s unregistered (#2)\n", |
| devices[i].dev->name); |
| } |
| } |
| } |
| |
| module_init(tvmixer_init_module); |
| module_exit(tvmixer_cleanup_module); |
| |
| /* |
| * Overrides for Emacs so that we follow Linus's tabbing style. |
| * --------------------------------------------------------------------------- |
| * Local variables: |
| * c-basic-offset: 8 |
| * End: |
| */ |