V4L/DVB (3661): Add wm8739 stereo audio ADC i2c driver
authorHans Verkuil <hverkuil@xs4all.nl>
Tue, 28 Mar 2006 21:23:48 +0000 (18:23 -0300)
committerMauro Carvalho Chehab <mchehab@infradead.org>
Sun, 2 Apr 2006 07:55:45 +0000 (04:55 -0300)
Add support for the Wolfson Microelectronics WM8739
stereo A/D converter from the ivtv driver.
Many thanks to Takahiro Adachi for writing the original driver.

Signed-off-by: Takahiro Adachi <tadachi@tadachi-net.com>
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
drivers/media/video/Kconfig
drivers/media/video/Makefile
drivers/media/video/wm8739.c [new file with mode: 0644]

index 730d816..e19c403 100644 (file)
@@ -343,15 +343,25 @@ config VIDEO_CS53L32A
          module will be called cs53l32a
 
 config VIDEO_WM8775
-       tristate "Wolfson Microelectronics WM8775 audio ADC"
+       tristate "Wolfson Microelectronics WM8775 audio ADC with input mixer"
        depends on VIDEO_DEV && I2C && EXPERIMENTAL
        ---help---
-         Support for the Wolfson Microelectronics WM8775
-         high performance stereo A/D Converter.
+         Support for the Wolfson Microelectronics WM8775 high
+         performance stereo A/D Converter with a 4 channel input mixer.
 
          To compile this driver as a module, choose M here: the
          module will be called wm8775
 
+config VIDEO_WM8739
+       tristate "Wolfson Microelectronics WM8739 stereo audio ADC"
+       depends on VIDEO_DEV && I2C && EXPERIMENTAL
+       ---help---
+         Support for the Wolfson Microelectronics WM8739
+         stereo A/D Converter.
+
+         To compile this driver as a module, choose M here: the
+         module will be called wm8739
+
 source "drivers/media/video/cx25840/Kconfig"
 
 config VIDEO_SAA711X
index e318b00..6cb2965 100644 (file)
@@ -45,6 +45,7 @@ obj-$(CONFIG_VIDEO_EM28XX) += tvp5150.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
 obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o
 obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
+obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
 obj-$(CONFIG_VIDEO_OVCAMCHIP) += ovcamchip/
 obj-$(CONFIG_VIDEO_CPIA2) += cpia2/
 obj-$(CONFIG_VIDEO_MXB) += saa7111.o tda9840.o tea6415c.o tea6420.o mxb.o
diff --git a/drivers/media/video/wm8739.c b/drivers/media/video/wm8739.c
new file mode 100644 (file)
index 0000000..a9b59c3
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+ * wm8739
+ *
+ * Copyright (C) 2005 T. Adachi <tadachi@tadachi-net.com>
+ *
+ * Copyright (C) 2005 Hans Verkuil <hverkuil@xs4all.nl>
+ * - Cleanup
+ *
+ * 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 <linux/module.h>
+#include <linux/types.h>
+#include <linux/ioctl.h>
+#include <asm/uaccess.h>
+#include <linux/i2c.h>
+#include <linux/i2c-id.h>
+#include <linux/videodev.h>
+#include <media/v4l2-common.h>
+
+MODULE_DESCRIPTION("wm8739 driver");
+MODULE_AUTHOR("T. Adachi, Hans Verkuil");
+MODULE_LICENSE("GPL");
+
+static int debug = 0;
+static unsigned short normal_i2c[] = { 0x34 >> 1, 0x36 >> 1, I2C_CLIENT_END };
+
+module_param(debug, int, 0644);
+
+MODULE_PARM_DESC(debug, "Debug level (0-1)");
+
+
+I2C_CLIENT_INSMOD;
+
+/* ------------------------------------------------------------------------ */
+
+enum {
+       R0 = 0, R1,
+       R5 = 5, R6, R7, R8, R9, R15 = 15,
+       TOT_REGS
+};
+
+struct wm8739_state {
+       u32 clock_freq;
+       u8 muted;
+       u16 volume;
+       u16 balance;
+       u8 vol_l;               /* +12dB to -34.5dB 1.5dB step (5bit) def:0dB */
+       u8 vol_r;               /* +12dB to -34.5dB 1.5dB step (5bit) def:0dB */
+};
+
+/* ------------------------------------------------------------------------ */
+
+static int wm8739_write(struct i2c_client *client, int reg, u16 val)
+{
+       int i;
+
+       if (reg < 0 || reg >= TOT_REGS) {
+               v4l_err(client, "Invalid register R%d\n", reg);
+               return -1;
+       }
+
+       v4l_dbg(1, debug, client, "write: %02x %02x\n", reg, val);
+
+       for (i = 0; i < 3; i++) {
+               if (i2c_smbus_write_byte_data(client, (reg << 1) |
+                                       (val >> 8), val & 0xff) == 0) {
+                       return 0;
+               }
+       }
+       v4l_err(client, "I2C: cannot write %03x to register R%d\n", val, reg);
+       return -1;
+}
+
+/* write regs to set audio volume etc */
+static void wm8739_set_audio(struct i2c_client *client)
+{
+       struct wm8739_state *state = i2c_get_clientdata(client);
+       u16 mute = state->muted ? 0x80 : 0;
+
+       /* Volume setting: bits 0-4, 0x1f = 12 dB, 0x00 = -34.5 dB
+        * Default setting: 0x17 = 0 dB
+        */
+       wm8739_write(client, R0, (state->vol_l & 0x1f) | mute);
+       wm8739_write(client, R1, (state->vol_r & 0x1f) | mute);
+}
+
+static int wm8739_get_ctrl(struct i2c_client *client, struct v4l2_control *ctrl)
+{
+       struct wm8739_state *state = i2c_get_clientdata(client);
+
+       switch (ctrl->id) {
+       case V4L2_CID_AUDIO_MUTE:
+               ctrl->value = state->muted;
+               break;
+
+       case V4L2_CID_AUDIO_VOLUME:
+               ctrl->value = state->volume;
+               break;
+
+       case V4L2_CID_AUDIO_BALANCE:
+               ctrl->value = state->balance;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int wm8739_set_ctrl(struct i2c_client *client, struct v4l2_control *ctrl)
+{
+       struct wm8739_state *state = i2c_get_clientdata(client);
+       unsigned int work_l, work_r;
+
+       switch (ctrl->id) {
+       case V4L2_CID_AUDIO_MUTE:
+               state->muted = ctrl->value;
+               break;
+
+       case V4L2_CID_AUDIO_VOLUME:
+               state->volume = ctrl->value;
+               break;
+
+       case V4L2_CID_AUDIO_BALANCE:
+               state->balance = ctrl->value;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       /* normalize ( 65535 to 0 -> 31 to 0 (12dB to -34.5dB) ) */
+       work_l = (min(65536 - state->balance, 32768) * state->volume) / 32768;
+       work_r = (min(state->balance, (u16)32768) * state->volume) / 32768;
+
+       state->vol_l = (long)work_l * 31 / 65535;
+       state->vol_r = (long)work_r * 31 / 65535;
+
+       /* set audio volume etc. */
+       wm8739_set_audio(client);
+       return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static struct v4l2_queryctrl wm8739_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_BALANCE,
+               .name          = "Balance",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 65535/100,
+               .default_value = 32768,
+               .flags         = 0,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       }
+};
+
+/* ------------------------------------------------------------------------ */
+
+static int wm8739_command(struct i2c_client *client, unsigned int cmd, void *arg)
+{
+       struct wm8739_state *state = i2c_get_clientdata(client);
+
+       switch (cmd) {
+       case VIDIOC_INT_AUDIO_CLOCK_FREQ:
+       {
+               u32 audiofreq = *(u32 *)arg;
+
+               state->clock_freq = audiofreq;
+               wm8739_write(client, R9, 0x000);        /* de-activate */
+               switch (audiofreq) {
+               case 44100:
+                       wm8739_write(client, R8, 0x020); /* 256fps, fs=44.1k     */
+                       break;
+               case 48000:
+                       wm8739_write(client, R8, 0x000); /* 256fps, fs=48k       */
+                       break;
+               case 32000:
+                       wm8739_write(client, R8, 0x018); /* 256fps, fs=32k       */
+                       break;
+               default:
+                       break;
+               }
+               wm8739_write(client, R9, 0x001);        /* activate */
+               break;
+       }
+
+       case VIDIOC_G_CTRL:
+               return wm8739_get_ctrl(client, arg);
+
+       case VIDIOC_S_CTRL:
+               return wm8739_set_ctrl(client, arg);
+
+       case VIDIOC_QUERYCTRL:
+       {
+               struct v4l2_queryctrl *qc = arg;
+               int i;
+
+               for (i = 0; i < ARRAY_SIZE(wm8739_qctrl); i++)
+                       if (qc->id && qc->id == wm8739_qctrl[i].id) {
+                               memcpy(qc, &wm8739_qctrl[i], sizeof(*qc));
+                               return 0;
+                       }
+               return -EINVAL;
+       }
+
+       case VIDIOC_LOG_STATUS:
+               v4l_info(client, "Frequency: %u Hz\n", state->clock_freq);
+               v4l_info(client, "Volume L:  %02x%s\n", state->vol_l & 0x1f,
+                               state->muted ? " (muted)" : "");
+               v4l_info(client, "Volume R:  %02x%s\n", state->vol_r & 0x1f,
+                               state->muted ? " (muted)" : "");
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* i2c implementation */
+
+static struct i2c_driver i2c_driver;
+
+static int wm8739_attach(struct i2c_adapter *adapter, int address, int kind)
+{
+       struct i2c_client *client;
+       struct wm8739_state *state;
+
+       /* Check if the adapter supports the needed features */
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+               return 0;
+
+       client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
+       if (client == NULL)
+               return -ENOMEM;
+
+       client->addr = address;
+       client->adapter = adapter;
+       client->driver = &i2c_driver;
+       snprintf(client->name, sizeof(client->name) - 1, "wm8739");
+
+       v4l_info(client, "chip found @ 0x%x (%s)\n", address << 1, adapter->name);
+
+       state = kmalloc(sizeof(struct wm8739_state), GFP_KERNEL);
+       if (state == NULL) {
+               kfree(client);
+               return -ENOMEM;
+       }
+       state->vol_l = 0x17; /* 0dB */
+       state->vol_r = 0x17; /* 0dB */
+       state->muted = 0;
+       state->balance = 32768;
+       /* normalize (12dB(31) to -34.5dB(0) [0dB(23)] -> 65535 to 0) */
+       state->volume = ((long)state->vol_l + 1) * 65535 / 31;
+       state->clock_freq = 48000;
+       i2c_set_clientdata(client, state);
+
+       /* initialize wm8739 */
+       wm8739_write(client, R15, 0x00); /* reset */
+       wm8739_write(client, R5, 0x000); /* filter setting, high path, offet clear */
+       wm8739_write(client, R6, 0x000); /* ADC, OSC, Power Off mode Disable */
+       wm8739_write(client, R7, 0x049); /* Digital Audio interface format */
+                                        /* Enable Master mode */
+                                        /* 24 bit, MSB first/left justified */
+       wm8739_write(client, R8, 0x000); /* sampling control */
+                                        /* normal, 256fs, 48KHz sampling rate */
+       wm8739_write(client, R9, 0x001); /* activate */
+       wm8739_set_audio(client);        /* set volume/mute */
+
+       i2c_attach_client(client);
+
+       return 0;
+}
+
+static int wm8739_probe(struct i2c_adapter *adapter)
+{
+       if (adapter->class & I2C_CLASS_TV_ANALOG)
+               return i2c_probe(adapter, &addr_data, wm8739_attach);
+       return 0;
+}
+
+static int wm8739_detach(struct i2c_client *client)
+{
+       int err;
+
+       err = i2c_detach_client(client);
+       if (err)
+               return err;
+
+       kfree(client);
+       return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* i2c implementation */
+static struct i2c_driver i2c_driver = {
+       .driver = {
+               .name = "wm8739",
+       },
+       .id = I2C_DRIVERID_WM8739,
+       .attach_adapter = wm8739_probe,
+       .detach_client  = wm8739_detach,
+       .command = wm8739_command,
+};
+
+
+static int __init wm8739_init_module(void)
+{
+       return i2c_add_driver(&i2c_driver);
+}
+
+static void __exit wm8739_cleanup_module(void)
+{
+       i2c_del_driver(&i2c_driver);
+}
+
+module_init(wm8739_init_module);
+module_exit(wm8739_cleanup_module);