All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/5] WL1273 FM Radio driver
@ 2010-07-16 10:27 Matti J. Aaltonen
  2010-07-16 10:27 ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
  0 siblings, 1 reply; 11+ messages in thread
From: Matti J. Aaltonen @ 2010-07-16 10:27 UTC (permalink / raw)
  To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen

Hello all,

and thanks for the comments Hans. NowIn this version there are several small fixes
because now I had time to actually test the driver... 

Hans wrote:
> I've been thinking about this a bit more. Would it be possible to do this automatically
> in the driver? I.e. based on the frequency you switch the device into the appropriate
> band?
> If that is not possible, then you shouldn't forget to document this new control in the spec.
> When you document it you should give some background information as well: the freq ranges of
> these bands and roughly where they are used.

What you suggest could of course be done but I think it would be kind of ugly especially
when doing HW scan and things like that. So I kept the bands and added it to the documentation.


drivers/mfd/wl1273-core.c 
> Have you verified that bits 0-2 correctly match the block numbering as defined
> by the spec? You should also copy bits 0-2 into bits 3-5. This is for backwards
> compatibility. Eventually we should be able to drop this, but for now we still
> need to do this.

Yes I think the block numbering is OK. Also added a copy to bits 3 to 5 etc...

drivers/media/radio/radio-wl1273.c
>> +     /* TODO: handle the case of multiple readers */
>
> Please remove this comment: multiple reader support does not belong in the kernel,
> so this will never happen.

Fixed.

>> +             return POLLIN | POLLRDNORM;
>
> Since you can write as well, shouldn't there be POLLOUT handling too?
>
>> +

Yes, fixed...


Cheers,
Matti


Matti J. Aaltonen (5):
  V4L2: Add seek spacing and FM RX class.
  MFD: WL1273 FM Radio: MFD driver for the FM radio.
  ASoC: WL1273 FM Radio Digital audio codec.
  V4L2: WL1273 FM Radio: Controls for the FM radio.
  Documentation: v4l: Add hw_seek spacing and FM_RX class

 Documentation/DocBook/v4l/controls.xml             |   71 +
 .../DocBook/v4l/vidioc-s-hw-freq-seek.xml          |   10 +-
 drivers/media/radio/Kconfig                        |   15 +
 drivers/media/radio/Makefile                       |    1 +
 drivers/media/radio/radio-wl1273.c                 | 1897 ++++++++++++++++++++
 drivers/mfd/Kconfig                                |    6 +
 drivers/mfd/Makefile                               |    2 +
 drivers/mfd/wl1273-core.c                          |  621 +++++++
 include/linux/mfd/wl1273-core.h                    |  313 ++++
 include/linux/videodev2.h                          |   15 +-
 sound/soc/codecs/Kconfig                           |    6 +
 sound/soc/codecs/Makefile                          |    2 +
 sound/soc/codecs/wl1273.c                          |  588 ++++++
 sound/soc/codecs/wl1273.h                          |   40 +
 14 files changed, 3584 insertions(+), 3 deletions(-)
 create mode 100644 drivers/media/radio/radio-wl1273.c
 create mode 100644 drivers/mfd/wl1273-core.c
 create mode 100644 include/linux/mfd/wl1273-core.h
 create mode 100644 sound/soc/codecs/wl1273.c
 create mode 100644 sound/soc/codecs/wl1273.h


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class.
  2010-07-16 10:27 [PATCH v5 0/5] WL1273 FM Radio driver Matti J. Aaltonen
@ 2010-07-16 10:27 ` Matti J. Aaltonen
  2010-07-16 10:27   ` [PATCH v5 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
                     ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Matti J. Aaltonen @ 2010-07-16 10:27 UTC (permalink / raw)
  To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen

Add spacing field to v4l2_hw_freq_seek and also add FM RX class to
control classes.

Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
---
 include/linux/videodev2.h |   15 ++++++++++++++-
 1 files changed, 14 insertions(+), 1 deletions(-)

diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
index 418dacf..95675cd 100644
--- a/include/linux/videodev2.h
+++ b/include/linux/videodev2.h
@@ -935,6 +935,7 @@ struct v4l2_ext_controls {
 #define V4L2_CTRL_CLASS_MPEG 0x00990000	/* MPEG-compression controls */
 #define V4L2_CTRL_CLASS_CAMERA 0x009a0000	/* Camera class controls */
 #define V4L2_CTRL_CLASS_FM_TX 0x009b0000	/* FM Modulator control class */
+#define V4L2_CTRL_CLASS_FM_RX 0x009c0000	/* FM Tuner control class */
 
 #define V4L2_CTRL_ID_MASK      	  (0x0fffffff)
 #define V4L2_CTRL_ID2CLASS(id)    ((id) & 0x0fff0000UL)
@@ -1313,6 +1314,17 @@ enum v4l2_preemphasis {
 #define V4L2_CID_TUNE_POWER_LEVEL		(V4L2_CID_FM_TX_CLASS_BASE + 113)
 #define V4L2_CID_TUNE_ANTENNA_CAPACITOR		(V4L2_CID_FM_TX_CLASS_BASE + 114)
 
+/* FM Tuner class control IDs */
+#define V4L2_CID_FM_RX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_RX | 0x900)
+#define V4L2_CID_FM_RX_CLASS			(V4L2_CTRL_CLASS_FM_RX | 1)
+
+#define V4L2_CID_FM_RX_BAND			(V4L2_CID_FM_TX_CLASS_BASE + 1)
+enum v4l2_fm_rx_band {
+	V4L2_FM_BAND_OTHER		= 0,
+	V4L2_FM_BAND_JAPAN		= 1,
+	V4L2_FM_BAND_OIRT		= 2
+};
+
 /*
  *	T U N I N G
  */
@@ -1377,7 +1389,8 @@ struct v4l2_hw_freq_seek {
 	enum v4l2_tuner_type  type;
 	__u32		      seek_upward;
 	__u32		      wrap_around;
-	__u32		      reserved[8];
+	__u32		      spacing;
+	__u32		      reserved[7];
 };
 
 /*
-- 
1.6.1.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v5 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
  2010-07-16 10:27 ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
@ 2010-07-16 10:27   ` Matti J. Aaltonen
  2010-07-16 10:27     ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
  2010-07-18  9:24   ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Hans Verkuil
  2010-07-18  9:55   ` Hans Verkuil
  2 siblings, 1 reply; 11+ messages in thread
From: Matti J. Aaltonen @ 2010-07-16 10:27 UTC (permalink / raw)
  To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen

This is a parent driver for two child drivers: the V4L2 driver and
the ALSA codec driver. The MFD part provides the I2C communication
to the device and a couple of functions that are called from both
children.

Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
---
 drivers/mfd/wl1273-core.c       |  621 +++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/wl1273-core.h |  313 ++++++++++++++++++++
 2 files changed, 934 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/wl1273-core.c
 create mode 100644 include/linux/mfd/wl1273-core.h

diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c
new file mode 100644
index 0000000..cb2fd78
--- /dev/null
+++ b/drivers/mfd/wl1273-core.c
@@ -0,0 +1,621 @@
+/*
+ * MFD driver for wl1273 FM radio and audio codec submodules.
+ *
+ * Author:	Matti Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * Copyright:   (C) 2010 Nokia Corporation
+ *
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#undef DEBUG
+
+#include <asm/unaligned.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/wl1273-core.h>
+#include <media/v4l2-common.h>
+
+#define DRIVER_DESC "WL1273 FM Radio Core"
+
+#define WL1273_IRQ_MASK	 (WL1273_FR_EVENT		|	\
+			  WL1273_POW_ENB_EVENT)
+
+static const struct band_info bands[] = {
+	/* USA & Europe */
+	{
+		.bottom_frequency	= 87500,
+		.top_frequency		= 108000,
+		.band			= V4L2_FM_BAND_OTHER,
+	},
+	/* Japan */
+	{
+		.bottom_frequency	= 76000,
+		.top_frequency		= 90000,
+		.band			= V4L2_FM_BAND_JAPAN,
+	},
+};
+
+/*
+ * static unsigned char radio_band - Band
+ *
+ * The bands are 0 == USA-Europe, 1 == Japan. USA-Europe is the default.
+ */
+static unsigned char radio_band;
+module_param(radio_band, byte, 0);
+MODULE_PARM_DESC(radio_band, "Band: 0=USA-Europe, 1=Japan");
+
+/*
+ * static unsigned int rds_buf - the number of RDS buffer blocks used.
+ *
+ * The default number is 100.
+ */
+static unsigned int rds_buf = 100;
+module_param(rds_buf, uint, 0);
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
+
+int wl1273_fm_read_reg(struct wl1273_core *core, u8 reg, u16 *value)
+{
+	struct i2c_client *client = core->i2c_dev;
+	u8 b[2];
+	int r;
+
+	r = i2c_smbus_read_i2c_block_data(client, reg, 2, b);
+	if (r != 2) {
+		dev_err(&client->dev, "%s: Read: %d fails.\n", __func__, reg);
+		return -EREMOTEIO;
+	}
+
+	*value = (u16)b[0] << 8 | b[1];
+
+	return 0;
+}
+EXPORT_SYMBOL(wl1273_fm_read_reg);
+
+int wl1273_fm_write_cmd(struct wl1273_core *core, u8 cmd, u16 param)
+{
+	struct i2c_client *client = core->i2c_dev;
+	u8 buf[] = { (param >> 8) & 0xff, param & 0xff };
+	int r;
+
+	r = i2c_smbus_write_i2c_block_data(client, cmd, 2, buf);
+	if (r) {
+		dev_err(&client->dev, "%s: Cmd: %d fails.\n", __func__, cmd);
+		return r;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(wl1273_fm_write_cmd);
+
+int wl1273_fm_write_data(struct wl1273_core *core, u8 *data, u16 len)
+{
+	struct i2c_client *client = core->i2c_dev;
+	struct i2c_msg msg[1];
+	int r;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = data;
+	msg[0].len = len;
+
+	r = i2c_transfer(client->adapter, msg, 1);
+
+	if (r != 1) {
+		dev_err(&client->dev, "%s: write error.\n", __func__);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(wl1273_fm_write_data);
+
+/**
+ * wl1273_fm_set_audio() -	Set audio mode.
+ * @core:			A pointer to the device struct.
+ * @new_mode:			The new audio mode.
+ *
+ * Audio modes are WL1273_AUDIO_DIGITAL and WL1273_AUDIO_ANALOG.
+ */
+int wl1273_fm_set_audio(struct wl1273_core *core, unsigned int new_mode)
+{
+	int r = 0;
+
+	if (core->mode == WL1273_MODE_OFF ||
+	    core->mode == WL1273_MODE_SUSPENDED)
+		return -EPERM;
+
+	if (core->mode == WL1273_MODE_RX && new_mode == WL1273_AUDIO_DIGITAL) {
+		r = wl1273_fm_write_cmd(core, WL1273_PCM_MODE_SET,
+					WL1273_PCM_DEF_MODE);
+		if (r)
+			goto out;
+
+		r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET,
+					core->i2s_mode);
+		if (r)
+			goto out;
+
+		r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE,
+					WL1273_AUDIO_ENABLE_I2S);
+		if (r)
+			goto out;
+
+	} else if (core->mode == WL1273_MODE_RX &&
+		   new_mode == WL1273_AUDIO_ANALOG) {
+		r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE,
+					WL1273_AUDIO_ENABLE_ANALOG);
+		if (r)
+			goto out;
+
+	} else if (core->mode == WL1273_MODE_TX &&
+		   new_mode == WL1273_AUDIO_DIGITAL) {
+		r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET,
+					core->i2s_mode);
+		if (r)
+			goto out;
+
+		r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET,
+					WL1273_AUDIO_IO_SET_I2S);
+		if (r)
+			goto out;
+
+	} else if (core->mode == WL1273_MODE_TX &&
+		   new_mode == WL1273_AUDIO_ANALOG) {
+		r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET,
+					WL1273_AUDIO_IO_SET_ANALOG);
+		if (r)
+			goto out;
+	}
+
+	core->audio_mode = new_mode;
+
+out:
+	return r;
+}
+EXPORT_SYMBOL(wl1273_fm_set_audio);
+
+/**
+ * wl1273_fm_set_volume() -	Set volume.
+ * @core:			A pointer to the device struct.
+ * @volume:			The new volume value.
+ */
+int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume)
+{
+	u16 val;
+	int r;
+
+	if (volume > WL1273_MAX_VOLUME)
+		return -EINVAL;
+
+	if (core->volume == volume)
+		return 0;
+
+	val = volume;
+	r = wl1273_fm_read_reg(core, WL1273_VOLUME_SET, &val);
+	if (r)
+		return r;
+
+	core->volume = volume;
+	return 0;
+}
+EXPORT_SYMBOL(wl1273_fm_set_volume);
+
+#define WL1273_FIFO_HAS_DATA(status)	(1 << 5 &  status)
+
+#define WL1273_RDS_CORRECTABLE_ERROR	(1 << 3)
+#define WL1273_RDS_UNCORRECTABLE_ERROR	(1 << 4)
+
+static int wl1273_fm_rds(struct wl1273_core *core)
+{
+	struct i2c_client *client = core->i2c_dev;
+	struct device *dev = &client->dev;
+	u16 val;
+	u8 b0[] = { WL1273_RDS_DATA_GET }, status;
+	struct v4l2_rds_data rds = { 0, 0, 0 };
+	struct i2c_msg msg[] = {
+		{
+			.addr = client->addr,
+			.flags = 0,
+			.buf = b0,
+			.len = sizeof(b0)
+		},
+		{
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.buf = (u8 *) &rds,
+			.len = sizeof(rds),
+		}
+	};
+	int r;
+
+	if (core->mode != WL1273_MODE_RX)
+		return 0;
+
+	r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
+	if (r)
+		return r;
+
+	if ((val & 0x01) == 0) {
+		/* RDS decoder not synchronized */
+		return -EAGAIN;
+	}
+
+	/* copy all four RDS blocks to internal buffer */
+	do {
+		r = i2c_transfer(client->adapter, msg, 2);
+		if (r != 2) {
+			dev_err(dev, WL1273_FM_DRIVER_NAME
+				": %s: read_rds error r == %i)\n",
+				__func__, r);
+		}
+
+		status = rds.block;
+
+		if (!WL1273_FIFO_HAS_DATA(status))
+			break;
+
+		/* fill bits 0-5 */
+		rds.block = 0x07 & status;
+		rds.block |= rds.block << 3;
+
+		/* copy the error bits to standard positions */
+		if (WL1273_RDS_UNCORRECTABLE_ERROR & status) {
+			rds.block |= V4L2_RDS_BLOCK_ERROR;
+			rds.block &= ~V4L2_RDS_BLOCK_CORRECTED;
+		} else if  (WL1273_RDS_CORRECTABLE_ERROR & status) {
+			rds.block &= ~V4L2_RDS_BLOCK_ERROR;
+			rds.block |= V4L2_RDS_BLOCK_CORRECTED;
+		}
+
+		/* copy RDS block to internal buffer */
+		memcpy(&core->buffer[core->wr_index], &rds, 3);
+		core->wr_index += 3;
+
+		/* wrap write pointer */
+		if (core->wr_index >= core->buf_size)
+			core->wr_index = 0;
+
+		/* check for overflow & start over */
+		if (core->wr_index == core->rd_index) {
+			dev_dbg(dev, "RDS OVERFLOW");
+
+			core->rd_index = 0;
+			core->wr_index = 0;
+			break;
+		}
+	} while (WL1273_FIFO_HAS_DATA(status));
+
+	/* wake up read queue */
+	if (core->wr_index != core->rd_index)
+		wake_up_interruptible(&core->read_queue);
+
+	return 0;
+}
+
+static void wl1273_fm_rds_work(struct wl1273_core *core)
+{
+	wl1273_fm_rds(core);
+}
+
+static irqreturn_t wl1273_fm_irq_thread_handler(int irq, void *dev_id)
+{
+	int r;
+	u16 flags;
+	struct wl1273_core *core = dev_id;
+
+	r = wl1273_fm_read_reg(core, WL1273_FLAG_GET, &flags);
+	if (r)
+		goto out;
+
+	if (flags & WL1273_BL_EVENT) {
+		core->irq_received = flags;
+		dev_dbg(&core->i2c_dev->dev, "IRQ: BL\n");
+	}
+
+	if (flags & WL1273_RDS_EVENT) {
+		msleep(200);
+
+		wl1273_fm_rds_work(core);
+	}
+
+	if (flags & WL1273_BBLK_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: BBLK\n");
+
+	if (flags & WL1273_LSYNC_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: LSYNC\n");
+
+	if (flags & WL1273_LEV_EVENT) {
+		u16 level;
+
+		r = wl1273_fm_read_reg(core, WL1273_RSSI_LVL_GET, &level);
+
+		if (r)
+			goto out;
+
+		if (level > 14)
+			dev_dbg(&core->i2c_dev->dev, "IRQ: LEV: 0x%x04\n",
+				level);
+	}
+
+	if (flags & WL1273_IFFR_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: IFFR\n");
+
+	if (flags & WL1273_PI_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: PI\n");
+
+	if (flags & WL1273_PD_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: PD\n");
+
+	if (flags & WL1273_STIC_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: STIC\n");
+
+	if (flags & WL1273_MAL_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: MAL\n");
+
+	if (flags & WL1273_POW_ENB_EVENT) {
+		complete(&core->busy);
+		dev_dbg(&core->i2c_dev->dev, "NOT BUSY\n");
+		dev_dbg(&core->i2c_dev->dev, "IRQ: POW_ENB\n");
+	}
+
+	if (flags & WL1273_SCAN_OVER_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: SCAN_OVER\n");
+
+	if (flags & WL1273_ERROR_EVENT)
+		dev_dbg(&core->i2c_dev->dev, "IRQ: ERROR\n");
+
+	if (flags & WL1273_FR_EVENT) {
+		u16 freq;
+
+		dev_dbg(&core->i2c_dev->dev, "IRQ: FR:\n");
+
+		if (core->mode == WL1273_MODE_RX) {
+			r = wl1273_fm_write_cmd(core, WL1273_TUNER_MODE_SET,
+						TUNER_MODE_STOP_SEARCH);
+			if (r) {
+				dev_err(&core->i2c_dev->dev,
+					"%s: TUNER_MODE_SET fails: %d\n",
+					__func__, r);
+				goto out;
+			}
+
+			r = wl1273_fm_read_reg(core, WL1273_FREQ_SET, &freq);
+			if (r)
+				goto out;
+
+			core->rx_frequency =
+				bands[core->band].bottom_frequency +
+				freq * 50;
+
+			/*
+			 *  The driver works better with this msleep,
+			 *  the documentation doesn't mention it.
+			 */
+			msleep(10);
+
+			dev_dbg(&core->i2c_dev->dev, "%dkHz\n",
+				core->rx_frequency);
+
+		} else {
+			r = wl1273_fm_read_reg(core, WL1273_CHANL_SET, &freq);
+			if (r)
+				goto out;
+
+			dev_dbg(&core->i2c_dev->dev, "%dkHz\n", freq);
+		}
+		dev_dbg(&core->i2c_dev->dev, "%s: NOT BUSY\n", __func__);
+	}
+
+out:
+	wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET,
+			    core->irq_flags);
+	complete(&core->busy);
+
+	return IRQ_HANDLED;
+}
+
+static struct i2c_device_id wl1273_driver_id_table[] = {
+	{ WL1273_FM_DRIVER_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, wl1273_driver_id_table);
+
+static int wl1273_core_remove(struct i2c_client *client)
+{
+	struct wl1273_core *core = i2c_get_clientdata(client);
+	struct wl1273_fm_platform_data *pdata =
+		client->dev.platform_data;
+
+	dev_dbg(&client->dev, "%s\n", __func__);
+
+	mfd_remove_devices(&client->dev);
+	i2c_set_clientdata(client, core);
+
+	free_irq(client->irq, core);
+	pdata->free_resources();
+
+	kfree(core->buffer);
+	kfree(core);
+
+	return 0;
+}
+
+static int __devinit wl1273_core_probe(struct i2c_client *client,
+				       const struct i2c_device_id *id)
+{
+	struct wl1273_fm_platform_data *pdata = client->dev.platform_data;
+	int r = 0;
+	struct wl1273_core *core;
+	int children = 0;
+
+	dev_dbg(&client->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(&client->dev, "No platform data.\n");
+		return -EINVAL;
+	}
+
+	core = kzalloc(sizeof(*core), GFP_KERNEL);
+	if (!core)
+		return -ENOMEM;
+
+	/* RDS buffer allocation */
+	core->buf_size = rds_buf * 3;
+	core->buffer = kmalloc(core->buf_size, GFP_KERNEL);
+	if (!core->buffer) {
+		dev_err(&client->dev,
+			"Cannot allocate memory for RDS buffer.\n");
+		r = -ENOMEM;
+		goto err_kmalloc;
+	}
+
+	core->irq_flags = WL1273_IRQ_MASK;
+	core->i2c_dev = client;
+	core->rds_on = false;
+	core->mode = WL1273_MODE_OFF;
+	core->tx_power = 4;
+	core->audio_mode = WL1273_AUDIO_ANALOG;
+	core->band = radio_band;
+	core->bands = bands;
+	core->number_of_bands = ARRAY_SIZE(bands);
+	core->i2s_mode = WL1273_I2S_DEF_MODE;
+	core->channel_number = 2;
+	core->volume = WL1273_DEFAULT_VOLUME;
+	core->rx_frequency = bands[core->band].bottom_frequency;
+	core->tx_frequency = bands[core->band].top_frequency;
+
+	dev_dbg(&client->dev, "radio_band: %d\n", radio_band);
+
+	mutex_init(&core->lock);
+
+	pdata = client->dev.platform_data;
+	if (pdata) {
+		r = pdata->request_resources(client);
+		if (r) {
+			dev_err(&client->dev, WL1273_FM_DRIVER_NAME
+				": Cannot get platform data\n");
+			goto err_new_mixer;
+		}
+
+		r = request_threaded_irq(client->irq, NULL,
+					 wl1273_fm_irq_thread_handler,
+					 IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+					 "wl1273-fm", core);
+		if (r < 0) {
+			dev_err(&client->dev, WL1273_FM_DRIVER_NAME
+				": Unable to register IRQ handler\n");
+			goto err_request_irq;
+		}
+	} else {
+		dev_err(&client->dev, WL1273_FM_DRIVER_NAME ": Core WL1273 IRQ"
+			" not configured");
+		r = -EINVAL;
+		goto err_new_mixer;
+	}
+
+	init_completion(&core->busy);
+	init_waitqueue_head(&core->read_queue);
+
+	i2c_set_clientdata(client, core);
+
+	if (pdata->children & WL1273_RADIO_CHILD) {
+		struct mfd_cell *cell = &core->cells[children];
+		dev_dbg(&client->dev, "%s: Have V4L2.\n", __func__);
+		cell->name = "wl1273_fm_radio";
+		cell->platform_data = &core;
+		cell->data_size = sizeof(core);
+		children++;
+	}
+
+	if (pdata->children & WL1273_CODEC_CHILD) {
+		struct mfd_cell *cell = &core->cells[children];
+		dev_dbg(&client->dev, "%s: Have codec.\n", __func__);
+		cell->name = "wl1273_codec_audio";
+		cell->platform_data = &core;
+		cell->data_size = sizeof(core);
+		children++;
+	}
+
+	if (children) {
+		dev_dbg(&client->dev, "%s: Have children.\n", __func__);
+		r = mfd_add_devices(&client->dev, -1, core->cells,
+				    children, NULL, 0);
+	} else {
+		dev_err(&client->dev, "No platform data found for children.\n");
+		r = -ENODEV;
+	}
+
+	if (!r)
+		return 0;
+
+	i2c_set_clientdata(client, NULL);
+	kfree(core);
+	free_irq(client->irq, core);
+err_request_irq:
+	pdata->free_resources();
+err_new_mixer:
+	kfree(core->buffer);
+err_kmalloc:
+	kfree(core);
+	dev_dbg(&client->dev, "%s\n", __func__);
+
+	return r;
+}
+
+static struct i2c_driver wl1273_core_driver = {
+	.driver = {
+		.name = WL1273_FM_DRIVER_NAME,
+	},
+	.probe = wl1273_core_probe,
+	.id_table = wl1273_driver_id_table,
+	.remove = __devexit_p(wl1273_core_remove),
+};
+
+static int __init wl1273_core_init(void)
+{
+	int r;
+
+	r = i2c_add_driver(&wl1273_core_driver);
+	if (r) {
+		pr_err(WL1273_FM_DRIVER_NAME
+		       ": driver registration failed\n");
+		return r;
+	}
+
+	return 0;
+}
+
+static void __exit wl1273_core_exit(void)
+{
+	flush_scheduled_work();
+
+	i2c_del_driver(&wl1273_core_driver);
+}
+late_initcall(wl1273_core_init);
+module_exit(wl1273_core_exit);
+
+MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/wl1273-core.h b/include/linux/mfd/wl1273-core.h
new file mode 100644
index 0000000..02fa427
--- /dev/null
+++ b/include/linux/mfd/wl1273-core.h
@@ -0,0 +1,313 @@
+/*
+ * include/media/radio/radio-wl1273.h
+ *
+ * Some definitions for the wl1273 radio receiver/transmitter chip.
+ *
+ * Copyright (C) Nokia Corporation
+ * Author: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef RADIO_WL1273_H
+#define RADIO_WL1273_H
+
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+
+#define WL1273_FM_DRIVER_NAME	"wl1273-fm"
+#define RX71_FM_I2C_ADDR	0x22
+
+#define WL1273_STEREO_GET		0
+#define WL1273_RSSI_LVL_GET		1
+#define WL1273_IF_COUNT_GET		2
+#define WL1273_FLAG_GET			3
+#define WL1273_RDS_SYNC_GET		4
+#define WL1273_RDS_DATA_GET		5
+#define WL1273_FREQ_SET			10
+#define WL1273_AF_FREQ_SET		11
+#define WL1273_MOST_MODE_SET		12
+#define WL1273_MOST_BLEND_SET		13
+#define WL1273_DEMPH_MODE_SET		14
+#define WL1273_SEARCH_LVL_SET		15
+#define WL1273_BAND_SET			16
+#define WL1273_MUTE_STATUS_SET		17
+#define WL1273_RDS_PAUSE_LVL_SET	18
+#define WL1273_RDS_PAUSE_DUR_SET	19
+#define WL1273_RDS_MEM_SET		20
+#define WL1273_RDS_BLK_B_SET		21
+#define WL1273_RDS_MSK_B_SET		22
+#define WL1273_RDS_PI_MASK_SET		23
+#define WL1273_RDS_PI_SET		24
+#define WL1273_RDS_SYSTEM_SET		25
+#define WL1273_INT_MASK_SET		26
+#define WL1273_SEARCH_DIR_SET		27
+#define WL1273_VOLUME_SET		28
+#define WL1273_AUDIO_ENABLE		29
+#define WL1273_PCM_MODE_SET		30
+#define WL1273_I2S_MODE_CONFIG_SET	31
+#define WL1273_POWER_SET		32
+#define WL1273_INTX_CONFIG_SET		33
+#define WL1273_PULL_EN_SET		34
+#define WL1273_HILO_SET			35
+#define WL1273_SWITCH2FREF		36
+#define WL1273_FREQ_DRIFT_REPORT	37
+
+#define WL1273_PCE_GET			40
+#define WL1273_FIRM_VER_GET		41
+#define WL1273_ASIC_VER_GET		42
+#define WL1273_ASIC_ID_GET		43
+#define WL1273_MAN_ID_GET		44
+#define WL1273_TUNER_MODE_SET		45
+#define WL1273_STOP_SEARCH		46
+#define WL1273_RDS_CNTRL_SET		47
+
+#define WL1273_WRITE_HARDWARE_REG	100
+#define WL1273_CODE_DOWNLOAD		101
+#define WL1273_RESET			102
+
+#define WL1273_FM_POWER_MODE		254
+#define WL1273_FM_INTERRUPT		255
+
+/* Transmitter API */
+
+#define WL1273_CHANL_SET		55
+#define WL1273_SCAN_SPACING_SET		56
+#define WL1273_REF_SET			57
+#define WL1273_POWER_ENB_SET		90
+#define WL1273_POWER_ATT_SET		58
+#define WL1273_POWER_LEV_SET		59
+#define WL1273_AUDIO_DEV_SET		60
+#define WL1273_PILOT_DEV_SET		61
+#define WL1273_RDS_DEV_SET		62
+#define WL1273_PUPD_SET			91
+#define WL1273_AUDIO_IO_SET		63
+#define WL1273_PREMPH_SET		64
+#define WL1273_MONO_SET			66
+#define WL1273_MUTE			92
+#define WL1273_MPX_LMT_ENABLE		67
+#define WL1273_PI_SET			93
+#define WL1273_ECC_SET			69
+#define WL1273_PTY			70
+#define WL1273_AF			71
+#define WL1273_DISPLAY_MODE		74
+#define WL1273_RDS_REP_SET		77
+#define WL1273_RDS_CONFIG_DATA_SET	98
+#define WL1273_RDS_DATA_SET		99
+#define WL1273_RDS_DATA_ENB		94
+#define WL1273_TA_SET			78
+#define WL1273_TP_SET			79
+#define WL1273_DI_SET			80
+#define WL1273_MS_SET			81
+#define WL1273_PS_SCROLL_SPEED		82
+#define WL1273_TX_AUDIO_LEVEL_TEST	96
+#define WL1273_TX_AUDIO_LEVEL_TEST_THRESHOLD	73
+#define WL1273_TX_AUDIO_INPUT_LEVEL_RANGE_SET	54
+#define WL1273_RX_ANTENNA_SELECT	87
+#define WL1273_I2C_DEV_ADDR_SET		86
+#define WL1273_REF_ERR_CALIB_PARAM_SET		88
+#define WL1273_REF_ERR_CALIB_PERIODICITY_SET	89
+#define WL1273_SOC_INT_TRIGGER			52
+#define WL1273_SOC_AUDIO_PATH_SET		83
+#define WL1273_SOC_PCMI_OVERRIDE		84
+#define WL1273_SOC_I2S_OVERRIDE		85
+#define WL1273_RSSI_BLOCK_SCAN_FREQ_SET	95
+#define WL1273_RSSI_BLOCK_SCAN_START	97
+#define WL1273_RSSI_BLOCK_SCAN_DATA_GET	 5
+#define WL1273_READ_FMANT_TUNE_VALUE		104
+
+#define WL1273_RDS_OFF		0
+#define WL1273_RDS_ON		1
+#define WL1273_RDS_RESET	2
+
+#define WL1273_AUDIO_DIGITAL	0
+#define WL1273_AUDIO_ANALOG	1
+
+#define WL1273_MODE_RX		(1 << 0)
+#define WL1273_MODE_TX		(1 << 1)
+#define WL1273_MODE_OFF		(1 << 2)
+#define WL1273_MODE_SUSPENDED	(1 << 3)
+
+#define WL1273_RADIO_CHILD	(1 << 0)
+#define WL1273_CODEC_CHILD	(1 << 1)
+
+#define WL1273_RX_MONO		1
+#define WL1273_RX_STEREO	0
+#define WL1273_TX_MONO		0
+#define WL1273_TX_STEREO	1
+
+#define WL1273_MAX_VOLUME	0xffff
+#define WL1273_DEFAULT_VOLUME	0x78b8
+
+/* I2S protocol, left channel first, data width 16 bits */
+#define WL1273_PCM_DEF_MODE		0x00
+
+/* Rx */
+#define WL1273_AUDIO_ENABLE_I2S		(1 << 0)
+#define WL1273_AUDIO_ENABLE_ANALOG	(1 << 1)
+
+/* Tx */
+#define WL1273_AUDIO_IO_SET_ANALOG	0
+#define WL1273_AUDIO_IO_SET_I2S		1
+
+#define WL1273_POWER_SET_OFF		0
+#define WL1273_POWER_SET_FM		(1 << 0)
+#define WL1273_POWER_SET_RDS		(1 << 1)
+#define WL1273_POWER_SET_RETENTION	(1 << 4)
+
+#define WL1273_PUPD_SET_OFF		0x00
+#define WL1273_PUPD_SET_ON		0x01
+#define WL1273_PUPD_SET_RETENTION	0x10
+
+/* I2S mode */
+#define WL1273_IS2_WIDTH_32	0x0
+#define WL1273_IS2_WIDTH_40	0x1
+#define WL1273_IS2_WIDTH_22_23	0x2
+#define WL1273_IS2_WIDTH_23_22	0x3
+#define WL1273_IS2_WIDTH_48	0x4
+#define WL1273_IS2_WIDTH_50	0x5
+#define WL1273_IS2_WIDTH_60	0x6
+#define WL1273_IS2_WIDTH_64	0x7
+#define WL1273_IS2_WIDTH_80	0x8
+#define WL1273_IS2_WIDTH_96	0x9
+#define WL1273_IS2_WIDTH_128	0xa
+#define WL1273_IS2_WIDTH	0xf
+
+#define WL1273_IS2_FORMAT_STD	(0x0 << 4)
+#define WL1273_IS2_FORMAT_LEFT	(0x1 << 4)
+#define WL1273_IS2_FORMAT_RIGHT	(0x2 << 4)
+#define WL1273_IS2_FORMAT_USER	(0x3 << 4)
+
+#define WL1273_IS2_MASTER	(0x0 << 6)
+#define WL1273_IS2_SLAVEW	(0x1 << 6)
+
+#define WL1273_IS2_TRI_AFTER_SENDING	(0x0 << 7)
+#define WL1273_IS2_TRI_ALWAYS_ACTIVE	(0x1 << 7)
+
+#define WL1273_IS2_SDOWS_RR	(0x0 << 8)
+#define WL1273_IS2_SDOWS_RF	(0x1 << 8)
+#define WL1273_IS2_SDOWS_FR	(0x2 << 8)
+#define WL1273_IS2_SDOWS_FF	(0x3 << 8)
+
+#define WL1273_IS2_TRI_OPT	(0x0 << 10)
+#define WL1273_IS2_TRI_ALWAYS	(0x1 << 10)
+
+#define WL1273_IS2_RATE_48K	(0x0 << 12)
+#define WL1273_IS2_RATE_44_1K	(0x1 << 12)
+#define WL1273_IS2_RATE_32K	(0x2 << 12)
+#define WL1273_IS2_RATE_22_05K	(0x4 << 12)
+#define WL1273_IS2_RATE_16K	(0x5 << 12)
+#define WL1273_IS2_RATE_12K	(0x8 << 12)
+#define WL1273_IS2_RATE_11_025	(0x9 << 12)
+#define WL1273_IS2_RATE_8K	(0xa << 12)
+#define WL1273_IS2_RATE		(0xf << 12)
+
+#define WL1273_I2S_DEF_MODE	(WL1273_IS2_WIDTH_32 | \
+				 WL1273_IS2_FORMAT_STD | \
+				 WL1273_IS2_MASTER | \
+				 WL1273_IS2_TRI_AFTER_SENDING | \
+				 WL1273_IS2_SDOWS_RR | \
+				 WL1273_IS2_TRI_OPT | \
+				 WL1273_IS2_RATE_48K)
+
+#define SCHAR_MIN (-128)
+#define SCHAR_MAX 127
+
+#define WL1273_FR_EVENT			(1 << 0)
+#define WL1273_BL_EVENT			(1 << 1)
+#define WL1273_RDS_EVENT		(1 << 2)
+#define WL1273_BBLK_EVENT		(1 << 3)
+#define WL1273_LSYNC_EVENT		(1 << 4)
+#define WL1273_LEV_EVENT		(1 << 5)
+#define WL1273_IFFR_EVENT		(1 << 6)
+#define WL1273_PI_EVENT			(1 << 7)
+#define WL1273_PD_EVENT			(1 << 8)
+#define WL1273_STIC_EVENT		(1 << 9)
+#define WL1273_MAL_EVENT		(1 << 10)
+#define WL1273_POW_ENB_EVENT		(1 << 11)
+#define WL1273_SCAN_OVER_EVENT		(1 << 12)
+#define WL1273_ERROR_EVENT		(1 << 13)
+
+#define TUNER_MODE_STOP_SEARCH		0
+#define TUNER_MODE_PRESET		1
+#define TUNER_MODE_AUTO_SEEK		2
+#define TUNER_MODE_AF			3
+#define TUNER_MODE_AUTO_SEEK_PI		4
+#define TUNER_MODE_AUTO_SEEK_BULK	5
+
+struct band_info {
+	u32 bottom_frequency;
+	u32 top_frequency;
+	u8 band;
+};
+
+struct wl1273_fm_platform_data {
+	int (*request_resources) (struct i2c_client *client);
+	void (*free_resources) (void);
+	void (*enable) (void);
+	void (*disable) (void);
+
+	u8 forbidden_modes;
+	unsigned int children;
+};
+
+#define WL1273_FM_CORE_CELLS	2
+
+struct wl1273_core {
+	struct mfd_cell cells[WL1273_FM_CORE_CELLS];
+	struct i2c_client *i2c_dev;
+
+	u8 forbidden;
+	unsigned int mode;
+	unsigned int preemphasis;
+	unsigned int audio_mode;
+	unsigned int spacing;
+	unsigned int tx_power;
+	unsigned int rx_frequency;
+	unsigned int tx_frequency;
+	unsigned int band;
+	unsigned int i2s_mode;
+	unsigned int channel_number;
+	unsigned int number_of_bands;
+	unsigned int volume;
+
+	const struct band_info *bands;
+
+	/* RDS */
+	bool rds_on;
+	struct delayed_work work;
+
+	wait_queue_head_t read_queue;
+	struct mutex lock; /* for serializing fm radio operations */
+	struct completion busy;
+
+	unsigned char *buffer;
+	unsigned int buf_size;
+	unsigned int rd_index;
+	unsigned int wr_index;
+
+	/* Selected interrupts */
+	u16 irq_flags;
+	u16 irq_received;
+};
+
+int wl1273_fm_write_cmd(struct wl1273_core *core, u8 cmd, u16 param);
+int wl1273_fm_write_data(struct wl1273_core *core, u8 *data, u16 len);
+int wl1273_fm_read_reg(struct wl1273_core *core, u8 reg, u16 *value);
+
+int wl1273_fm_set_audio(struct wl1273_core *core, unsigned int mode);
+int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume);
+
+#endif	/* ifndef RADIO_WL1273_H */
-- 
1.6.1.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec.
  2010-07-16 10:27   ` [PATCH v5 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
@ 2010-07-16 10:27     ` Matti J. Aaltonen
  2010-07-16 10:27       ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
  2010-07-18  9:28       ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Hans Verkuil
  0 siblings, 2 replies; 11+ messages in thread
From: Matti J. Aaltonen @ 2010-07-16 10:27 UTC (permalink / raw)
  To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen

The codec handles digital audio input to and output from the
WL1273 FM radio.

Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
---
 sound/soc/codecs/Kconfig  |    6 +
 sound/soc/codecs/Makefile |    2 +
 sound/soc/codecs/wl1273.c |  588 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wl1273.h |   40 +++
 4 files changed, 636 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/codecs/wl1273.c
 create mode 100644 sound/soc/codecs/wl1273.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 52b005f..c4769f2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -35,6 +35,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_TWL4030 if TWL4030_CORE
 	select SND_SOC_UDA134X
 	select SND_SOC_UDA1380 if I2C
+	select SND_SOC_WL1273 if I2C
 	select SND_SOC_WM8350 if MFD_WM8350
 	select SND_SOC_WM8400 if MFD_WM8400
 	select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
@@ -161,6 +162,11 @@ config SND_SOC_UDA134X
 config SND_SOC_UDA1380
         tristate
 
+config SND_SOC_WL1273
+	tristate
+	select WL1273_CORE
+	default n
+
 config SND_SOC_WM8350
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index dbaecb1..2a7c564 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -22,6 +22,7 @@ snd-soc-tlv320dac33-objs := tlv320dac33.o
 snd-soc-twl4030-objs := twl4030.o
 snd-soc-uda134x-objs := uda134x.o
 snd-soc-uda1380-objs := uda1380.o
+snd-soc-wl1273-objs := wl1273.o
 snd-soc-wm8350-objs := wm8350.o
 snd-soc-wm8400-objs := wm8400.o
 snd-soc-wm8510-objs := wm8510.o
@@ -78,6 +79,7 @@ obj-$(CONFIG_SND_SOC_TLV320DAC33)	+= snd-soc-tlv320dac33.o
 obj-$(CONFIG_SND_SOC_TWL4030)	+= snd-soc-twl4030.o
 obj-$(CONFIG_SND_SOC_UDA134X)	+= snd-soc-uda134x.o
 obj-$(CONFIG_SND_SOC_UDA1380)	+= snd-soc-uda1380.o
+obj-$(CONFIG_SND_SOC_WL1273)	+= snd-soc-wl1273.o
 obj-$(CONFIG_SND_SOC_WM8350)	+= snd-soc-wm8350.o
 obj-$(CONFIG_SND_SOC_WM8400)	+= snd-soc-wm8400.o
 obj-$(CONFIG_SND_SOC_WM8510)	+= snd-soc-wm8510.o
diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c
new file mode 100644
index 0000000..ab36c0b
--- /dev/null
+++ b/sound/soc/codecs/wl1273.c
@@ -0,0 +1,588 @@
+/*
+ * ALSA SoC WL1273 codec driver
+ *
+ * Author:      Matti Aaltonen, <matti.j.aaltonen@nokia.com>
+ *
+ * Copyright:   (C) 2010 Nokia Corporation
+ *
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#undef DEBUG
+
+#include <linux/mfd/wl1273-core.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "wl1273.h"
+
+static int snd_wl1273_fm_set_i2s_mode(struct wl1273_core *core,
+				      int rate, int width)
+{
+	struct device *dev = &core->i2c_dev->dev;
+	int r = 0;
+	u16 mode;
+
+	dev_dbg(dev, "rate: %d\n", rate);
+	dev_dbg(dev, "width: %d\n", width);
+
+	mutex_lock(&core->lock);
+
+	mode = core->i2s_mode & ~WL1273_IS2_WIDTH & ~WL1273_IS2_RATE;
+
+	switch (rate) {
+	case 48000:
+		mode |= WL1273_IS2_RATE_48K;
+		break;
+	case 44100:
+		mode |= WL1273_IS2_RATE_44_1K;
+		break;
+	case 32000:
+		mode |= WL1273_IS2_RATE_32K;
+		break;
+	case 22050:
+		mode |= WL1273_IS2_RATE_22_05K;
+		break;
+	case 16000:
+		mode |= WL1273_IS2_RATE_16K;
+		break;
+	case 12000:
+		mode |= WL1273_IS2_RATE_12K;
+		break;
+	case 11025:
+		mode |= WL1273_IS2_RATE_11_025;
+		break;
+	case 8000:
+		mode |= WL1273_IS2_RATE_8K;
+		break;
+	default:
+		dev_err(dev, "Sampling rate: %d not supported\n", rate);
+		r = -EINVAL;
+		goto out;
+	}
+
+	switch (width) {
+	case 16:
+		mode |= WL1273_IS2_WIDTH_32;
+		break;
+	case 20:
+		mode |= WL1273_IS2_WIDTH_40;
+		break;
+	case 24:
+		mode |= WL1273_IS2_WIDTH_48;
+		break;
+	case 25:
+		mode |= WL1273_IS2_WIDTH_50;
+		break;
+	case 30:
+		mode |= WL1273_IS2_WIDTH_60;
+		break;
+	case 32:
+		mode |= WL1273_IS2_WIDTH_64;
+		break;
+	case 40:
+		mode |= WL1273_IS2_WIDTH_80;
+		break;
+	case 48:
+		mode |= WL1273_IS2_WIDTH_96;
+		break;
+	case 64:
+		mode |= WL1273_IS2_WIDTH_128;
+		break;
+	default:
+		dev_err(dev, "Data width: %d not supported\n", width);
+		r = -EINVAL;
+		goto out;
+	}
+
+	dev_dbg(dev, "WL1273_I2S_DEF_MODE: 0x%04x\n",  WL1273_I2S_DEF_MODE);
+	dev_dbg(dev, "core->i2s_mode: 0x%04x\n", core->i2s_mode);
+	dev_dbg(dev, "mode: 0x%04x\n", mode);
+
+	if (core->i2s_mode != mode) {
+		r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET,
+					mode);
+		if (!r)
+			core->i2s_mode = mode;
+
+		r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE,
+					WL1273_AUDIO_ENABLE_I2S);
+		if (r)
+			goto out;
+	}
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int snd_wl1273_fm_set_channel_number(struct wl1273_core *core,
+					    int channel_number)
+{
+	struct i2c_client *client = core->i2c_dev;
+	struct device *dev = &client->dev;
+	int r = 0;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	mutex_lock(&core->lock);
+
+	if (core->channel_number == channel_number)
+		goto out;
+
+	if (channel_number == 1 && core->mode == WL1273_MODE_RX)
+		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
+					WL1273_RX_MONO);
+	else if (channel_number == 1 && core->mode == WL1273_MODE_TX)
+		r = wl1273_fm_write_cmd(core, WL1273_MONO_SET,
+					WL1273_TX_MONO);
+	else if (channel_number == 2 && core->mode == WL1273_MODE_RX)
+		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
+					WL1273_RX_STEREO);
+	else if (channel_number == 2 && core->mode == WL1273_MODE_TX)
+		r = wl1273_fm_write_cmd(core, WL1273_MONO_SET,
+					WL1273_TX_STEREO);
+	else
+		r = -EINVAL;
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int snd_wl1273_get_audio_route(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = codec->private_data;
+
+	ucontrol->value.integer.value[0] = wl1273->mode;
+
+	return 0;
+}
+
+static int snd_wl1273_set_audio_route(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = codec->private_data;
+
+	/* Do not allow changes while stream is running */
+	if (codec->active)
+		return -EPERM;
+
+	wl1273->mode = ucontrol->value.integer.value[0];
+
+	return 1;
+}
+
+static const char *wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" };
+
+static const struct soc_enum wl1273_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_route), wl1273_audio_route),
+};
+
+static int snd_wl1273_fm_audio_get(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = codec->private_data;
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	ucontrol->value.integer.value[0] = wl1273->core->audio_mode;
+
+	return 0;
+}
+
+static int snd_wl1273_fm_audio_put(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = codec->private_data;
+	int val, r = 0;
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	val = ucontrol->value.integer.value[0];
+	if (wl1273->core->audio_mode == val)
+		return 0;
+
+	r = wl1273_fm_set_audio(wl1273->core, val);
+	if (r < 0)
+		return r;
+
+	return 1;
+}
+
+static const char *wl1273_audio_strings[] = { "Digital", "Analog" };
+
+static const struct soc_enum wl1273_audio_enum[] = {
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_strings),
+			    wl1273_audio_strings),
+};
+
+static int snd_wl1273_fm_volume_get(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = codec->private_data;
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	ucontrol->value.integer.value[0] = wl1273->core->volume;
+
+	return 0;
+}
+
+static int snd_wl1273_fm_volume_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct wl1273_priv *wl1273 = codec->private_data;
+	int r;
+
+	dev_dbg(codec->dev, "%s: enter.\n", __func__);
+
+	r = wl1273_fm_set_volume(wl1273->core,
+				 ucontrol->value.integer.value[0]);
+	if (r)
+		return r;
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new wl1273_controls[] = {
+	SOC_ENUM_EXT("Codec Mode", wl1273_enum[0],
+		     snd_wl1273_get_audio_route, snd_wl1273_set_audio_route),
+	SOC_ENUM_EXT("Audio Switch", wl1273_audio_enum[0],
+		     snd_wl1273_fm_audio_get,  snd_wl1273_fm_audio_put),
+	SOC_SINGLE_EXT("Volume", 0, 0, WL1273_MAX_VOLUME, 0,
+		       snd_wl1273_fm_volume_get, snd_wl1273_fm_volume_put),
+};
+
+static int wl1273_add_controls(struct snd_soc_codec *codec)
+{
+	return snd_soc_add_controls(codec, wl1273_controls,
+				    ARRAY_SIZE(wl1273_controls));
+}
+
+static int wl1273_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_pcm *pcm = socdev->card->dai_link->pcm;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct wl1273_priv *wl1273 = codec->private_data;
+
+	switch (wl1273->mode) {
+	case WL1273_MODE_BT:
+		pcm->info_flags &= ~SNDRV_PCM_INFO_HALF_DUPLEX;
+		break;
+	case WL1273_MODE_FM_RX:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			pr_err("Cannot play in RX mode.\n");
+			return -EINVAL;
+		}
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+		break;
+	case WL1273_MODE_FM_TX:
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+			pr_err("Cannot capture in TX mode.\n");
+			return -EINVAL;
+		}
+		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+
+	return 0;
+}
+
+static int wl1273_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct wl1273_priv *wl1273 = codec->private_data;
+	struct wl1273_core *core = wl1273->core;
+	unsigned int rate, width, r;
+
+	if (params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
+		pr_err("Only SNDRV_PCM_FORMAT_S16_LE supported.\n");
+		return -EINVAL;
+	}
+
+	rate = params_rate(params);
+	width =  hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+
+	if (wl1273->mode == WL1273_MODE_BT) {
+		if (rate != 8000) {
+			pr_err("Rate %d not supported.\n", params_rate(params));
+			return -EINVAL;
+		}
+
+		if (params_channels(params) != 1) {
+			pr_err("Only mono supported.\n");
+			return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	if (wl1273->mode == WL1273_MODE_FM_TX &&
+	    substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		pr_err("Only playback supported with TX.\n");
+		return -EINVAL;
+	}
+
+	if (wl1273->mode == WL1273_MODE_FM_RX  &&
+	    substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		pr_err("Only capture supported with RX.\n");
+		return -EINVAL;
+	}
+
+	if (wl1273->mode != WL1273_MODE_FM_RX  &&
+	    wl1273->mode != WL1273_MODE_FM_TX) {
+		pr_err("Unexpected mode: %d.\n", wl1273->mode);
+		return -EINVAL;
+	}
+
+	r = snd_wl1273_fm_set_i2s_mode(core, rate, width);
+	if (r)
+		return r;
+
+	r = snd_wl1273_fm_set_channel_number(core, (params_channels(params)));
+	if (r)
+		return r;
+
+	return 0;
+}
+
+static int wl1273_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			      unsigned int fmt)
+{
+	return 0;
+}
+
+static struct snd_soc_dai_ops wl1273_dai_ops = {
+	.startup	= wl1273_startup,
+	.hw_params	= wl1273_hw_params,
+	.set_fmt	= wl1273_set_dai_fmt,
+};
+
+struct snd_soc_dai wl1273_dai = {
+	.name = "WL1273 BT/FM codec",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE},
+	.ops = &wl1273_dai_ops,
+};
+EXPORT_SYMBOL_GPL(wl1273_dai);
+
+static int wl1273_soc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	return 0;
+}
+
+static int wl1273_soc_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct snd_soc_codec *wl1273_codec;
+
+/*
+ * initialize the driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int wl1273_soc_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec;
+	struct wl1273_priv *wl1273;
+	int r = 0;
+
+	dev_dbg(&pdev->dev, "%s.\n", __func__);
+
+	codec = wl1273_codec;
+	wl1273 = codec->private_data;
+	socdev->card->codec = codec;
+
+	codec->name = "wl1273";
+	codec->owner = THIS_MODULE;
+	codec->dai = &wl1273_dai;
+	codec->num_dai = 1;
+
+	/* register pcms */
+	r = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (r < 0) {
+		dev_err(&pdev->dev, "Wl1273: failed to create pcms.\n");
+		goto err2;
+	}
+
+	r = wl1273_add_controls(codec);
+	if (r < 0) {
+		dev_err(&pdev->dev, "Wl1273: failed to add contols.\n");
+		goto err1;
+	}
+
+	return r;
+
+err1:
+	snd_soc_free_pcms(socdev);
+err2:
+	return r;
+}
+
+static int wl1273_soc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+	snd_soc_free_pcms(socdev);
+
+	return 0;
+}
+
+static int __devinit wl1273_codec_probe(struct platform_device *pdev)
+{
+	struct wl1273_core **pdata = pdev->dev.platform_data;
+	struct snd_soc_codec *codec;
+	struct wl1273_priv *wl1273;
+	int r;
+
+	dev_dbg(&pdev->dev, "%s.\n", __func__);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "Platform data is missing.\n");
+		return -EINVAL;
+	}
+
+	wl1273 = kzalloc(sizeof(struct wl1273_priv), GFP_KERNEL);
+	if (wl1273 == NULL) {
+		dev_err(&pdev->dev, "Cannot allocate memory.\n");
+		return -ENOMEM;
+	}
+
+	wl1273->mode = WL1273_MODE_BT;
+	wl1273->core = *pdata;
+
+	codec = &wl1273->codec;
+	codec->private_data = wl1273;
+	codec->dev = &pdev->dev;
+	wl1273_dai.dev = &pdev->dev;
+
+	mutex_init(&codec->mutex);
+	INIT_LIST_HEAD(&codec->dapm_widgets);
+	INIT_LIST_HEAD(&codec->dapm_paths);
+
+	codec->name = "wl1273";
+	codec->owner = THIS_MODULE;
+	codec->dai = &wl1273_dai;
+	codec->num_dai = 1;
+
+	platform_set_drvdata(pdev, wl1273);
+	wl1273_codec = codec;
+
+	codec->bias_level = SND_SOC_BIAS_OFF;
+
+	r = snd_soc_register_codec(codec);
+	if (r != 0) {
+		dev_err(codec->dev, "Failed to register codec: %d\n", r);
+		goto err2;
+	}
+
+	r = snd_soc_register_dai(&wl1273_dai);
+	if (r != 0) {
+		dev_err(codec->dev, "Failed to register DAIs: %d\n", r);
+		goto err1;
+	}
+
+	return 0;
+
+err1:
+	snd_soc_unregister_codec(codec);
+err2:
+	kfree(wl1273);
+	return r;
+}
+
+static int __devexit wl1273_codec_remove(struct platform_device *pdev)
+{
+	struct wl1273_priv *wl1273 = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	snd_soc_unregister_dai(&wl1273_dai);
+	snd_soc_unregister_codec(&wl1273->codec);
+
+	kfree(wl1273);
+	wl1273_codec = NULL;
+
+	return 0;
+}
+
+MODULE_ALIAS("platform:wl1273_codec_audio");
+
+static struct platform_driver wl1273_codec_driver = {
+	.probe		= wl1273_codec_probe,
+	.remove		= __devexit_p(wl1273_codec_remove),
+	.driver		= {
+		.name	= "wl1273_codec_audio",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init wl1273_modinit(void)
+{
+	return platform_driver_register(&wl1273_codec_driver);
+}
+module_init(wl1273_modinit);
+
+static void __exit wl1273_exit(void)
+{
+	platform_driver_unregister(&wl1273_codec_driver);
+}
+module_exit(wl1273_exit);
+
+struct snd_soc_codec_device soc_codec_dev_wl1273 = {
+	.probe = wl1273_soc_probe,
+	.remove = wl1273_soc_remove,
+	.suspend = wl1273_soc_suspend,
+	.resume = wl1273_soc_resume};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wl1273);
+
+MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
+MODULE_DESCRIPTION("ASoC WL1273 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wl1273.h b/sound/soc/codecs/wl1273.h
new file mode 100644
index 0000000..5599817
--- /dev/null
+++ b/sound/soc/codecs/wl1273.h
@@ -0,0 +1,40 @@
+/*
+ * sound/soc/codec/wl1273.h
+ *
+ * ALSA SoC WL1273 codec driver
+ *
+ * Copyright (C) Nokia Corporation
+ * Author: Matti Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * 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.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __WL1273_CODEC_H__
+#define __WL1273_CODEC_H__
+
+enum wl1273_mode { WL1273_MODE_BT, WL1273_MODE_FM_RX, WL1273_MODE_FM_TX };
+
+/* codec private data */
+struct wl1273_priv {
+	struct snd_soc_codec codec;
+	enum wl1273_mode mode;
+	struct wl1273_core *core;
+};
+
+extern struct snd_soc_dai wl1273_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wl1273;
+
+#endif	/* End of __WL1273_CODEC_H__ */
-- 
1.6.1.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio.
  2010-07-16 10:27     ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
@ 2010-07-16 10:27       ` Matti J. Aaltonen
  2010-07-16 10:27         ` [PATCH v5 5/5] Documentation: v4l: Add hw_seek spacing and FM_RX class Matti J. Aaltonen
  2010-07-18  9:52         ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Hans Verkuil
  2010-07-18  9:28       ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Hans Verkuil
  1 sibling, 2 replies; 11+ messages in thread
From: Matti J. Aaltonen @ 2010-07-16 10:27 UTC (permalink / raw)
  To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen

This file implements V4L2 controls for using the Texas Instruments
WL1273 FM Radio.

Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
---
 drivers/media/radio/Kconfig        |   15 +
 drivers/media/radio/Makefile       |    1 +
 drivers/media/radio/radio-wl1273.c | 1897 ++++++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig                |    6 +
 drivers/mfd/Makefile               |    2 +
 5 files changed, 1921 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/radio/radio-wl1273.c

diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 83567b8..209fd37 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -452,4 +452,19 @@ config RADIO_TIMBERDALE
 	  found behind the Timberdale FPGA on the Russellville board.
 	  Enabling this driver will automatically select the DSP and tuner.
 
+config RADIO_WL1273
+	tristate "Texas Instruments WL1273 I2C FM Radio"
+        depends on I2C && VIDEO_V4L2 && SND
+	select FW_LOADER
+	---help---
+	  Choose Y here if you have this FM radio chip.
+
+	  In order to control your radio card, you will need to use programs
+	  that are compatible with the Video For Linux 2 API.  Information on
+	  this API and pointers to "v4l2" programs may be found at
+	  <file:Documentation/video4linux/API.html>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called radio-wl1273.
+
 endif # RADIO_ADAPTERS
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index f615583..d297074 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -26,5 +26,6 @@ obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
 obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o
 obj-$(CONFIG_RADIO_TEF6862) += tef6862.o
 obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o
+obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o
 
 EXTRA_CFLAGS += -Isound
diff --git a/drivers/media/radio/radio-wl1273.c b/drivers/media/radio/radio-wl1273.c
new file mode 100644
index 0000000..66ef841
--- /dev/null
+++ b/drivers/media/radio/radio-wl1273.c
@@ -0,0 +1,1897 @@
+/*
+ * Driver for the Texas Instruments WL1273 FM radio.
+ *
+ * Copyright (C) Nokia Corporation
+ * Author: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#undef DEBUG
+
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/mfd/wl1273-core.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+
+#define DRIVER_DESC "Wl1273 FM Radio - V4L2"
+
+#define WL1273_POWER_SET_OFF		0
+#define WL1273_POWER_SET_FM		(1 << 0)
+#define WL1273_POWER_SET_RDS		(1 << 1)
+#define WL1273_POWER_SET_RETENTION	(1 << 4)
+
+#define WL1273_PUPD_SET_OFF		0x00
+#define WL1273_PUPD_SET_ON		0x01
+#define WL1273_PUPD_SET_RETENTION	0x10
+
+#define WL1273_FREQ(x)		(x * 10000 / 625)
+#define WL1273_INV_FREQ(x)	(x * 625 / 10000)
+
+/*
+ * static int radio_nr - The number of the radio device
+ *
+ * The default is 0.
+ */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+struct wl1273_device {
+	struct v4l2_device v4l2dev;
+	struct video_device videodev;
+	struct device *dev;
+	struct wl1273_core *core;
+	struct file *owner;
+	char *write_buf;
+	bool rds_on;
+};
+
+static int wl1273_fm_set_tx_freq(struct wl1273_core *core, unsigned int freq)
+{
+	int r = 0;
+
+	if (freq < 76000) {
+		dev_err(&core->i2c_dev->dev,
+			"Frequency out of range: %d < %d\n",
+			freq, core->bands[core->band].bottom_frequency);
+		return -EDOM;
+	}
+
+	if (freq > 108000) {
+		dev_err(&core->i2c_dev->dev,
+			"Frequency out of range: %d > %d\n",
+			freq, core->bands[core->band].top_frequency);
+		return -EDOM;
+	}
+
+	/*
+	 *  The driver works better with this msleep,
+	 *  the documentation doesn't mention it.
+	 */
+	msleep(5);
+	dev_dbg(&core->i2c_dev->dev, "%s: freq: %d kHz\n", __func__, freq);
+
+	INIT_COMPLETION(core->busy);
+	/* Set the current tx channel */
+	r = wl1273_fm_write_cmd(core, WL1273_CHANL_SET, freq / 10);
+	if (r)
+		return r;
+
+	/* wait for the FR IRQ */
+	r = wait_for_completion_timeout(&core->busy, msecs_to_jiffies(1000));
+	if (!r)
+		return -ETIMEDOUT;
+
+	dev_dbg(&core->i2c_dev->dev, "WL1273_CHANL_SET: %d\n", r);
+
+	/* Enable the output power */
+	INIT_COMPLETION(core->busy);
+	r = wl1273_fm_write_cmd(core, WL1273_POWER_ENB_SET, 1);
+	if (r)
+		return r;
+
+	/* wait for the POWER_ENB IRQ */
+	r = wait_for_completion_timeout(&core->busy, msecs_to_jiffies(1000));
+	if (!r)
+		return -ETIMEDOUT;
+
+	core->tx_frequency = freq;
+	dev_dbg(&core->i2c_dev->dev, "WL1273_POWER_ENB_SET: %d\n", r);
+
+	return	0;
+}
+
+static int wl1273_fm_set_rx_freq(struct wl1273_core *core, unsigned int freq)
+{
+	int r;
+	int f;
+
+	if (freq < core->bands[core->band].bottom_frequency) {
+		dev_err(&core->i2c_dev->dev,
+			"Frequency out of range: %d < %d\n",
+			freq, core->bands[core->band].bottom_frequency);
+		r = -EDOM;
+		goto err;
+	}
+
+	if (freq > core->bands[core->band].top_frequency) {
+		dev_err(&core->i2c_dev->dev,
+			"Frequency out of range: %d > %d\n",
+			freq, core->bands[core->band].top_frequency);
+		r = -EDOM;
+		goto err;
+	}
+
+	dev_dbg(&core->i2c_dev->dev, "%s: %dkHz\n", __func__, freq);
+
+	wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET, core->irq_flags);
+
+	f = (freq - core->bands[core->band].bottom_frequency) / 50;
+	r = wl1273_fm_write_cmd(core, WL1273_FREQ_SET, f);
+	if (r) {
+		dev_err(&core->i2c_dev->dev, "FREQ_SET fails\n");
+		goto err;
+	}
+
+	INIT_COMPLETION(core->busy);
+	r = wl1273_fm_write_cmd(core, WL1273_TUNER_MODE_SET, TUNER_MODE_PRESET);
+	if (r) {
+		dev_err(&core->i2c_dev->dev, "TUNER_MODE_SET fails\n");
+		complete(&core->busy);
+		goto err;
+	}
+
+	r = wait_for_completion_timeout(&core->busy, msecs_to_jiffies(2000));
+	if (!r) {
+		dev_err(&core->i2c_dev->dev, "TIMEOUT\n");
+		return -ETIMEDOUT;
+	}
+
+	core->rd_index = 0;
+	core->wr_index = 0;
+	core->rx_frequency = freq;
+	return 0;
+
+err:
+	return r;
+}
+
+static int wl1273_fm_get_freq(struct wl1273_core *core)
+{
+	unsigned int freq;
+	u16 f;
+	int r;
+
+	if (core->mode == WL1273_MODE_RX) {
+		r = wl1273_fm_read_reg(core, WL1273_FREQ_SET, &f);
+		if (r)
+			return r;
+
+		dev_dbg(&core->i2c_dev->dev, "Freq get: 0x%04x\n", f);
+		freq = core->bands[core->band].bottom_frequency + 50 * f;
+	} else {
+		r = wl1273_fm_read_reg(core, WL1273_CHANL_SET, &f);
+		if (r)
+			return r;
+
+		freq = f * 10;
+	}
+
+	return freq;
+}
+
+/**
+ * wl1273_fm_upload_firmware_patch() -	Upload the firmware.
+ * @core:				A pointer to the device struct.
+ *
+ * The firmware file consists of arrays of bytes where the first byte
+ * gives the array length. The first byte in the file gives the
+ * number of these arrays.
+ */
+static int wl1273_fm_upload_firmware_patch(struct wl1273_core *core)
+{
+	unsigned int packet_num;
+	const struct firmware *fw_p;
+	const char *fw_name = "radio-wl1273-fw.bin";
+	struct i2c_client *client;
+	__u8 *ptr;
+	int i, n, len, r;
+	struct i2c_msg *msgs;
+
+	client = core->i2c_dev;
+	dev_dbg(&client->dev, "%s:\n", __func__);
+
+	if (request_firmware(&fw_p, fw_name, &client->dev)) {
+		dev_info(&client->dev, "%s - %s not found\n", __func__,
+			 fw_name);
+
+		return 0;
+	}
+
+	ptr = (__u8 *) fw_p->data;
+	packet_num = ptr[0];
+	dev_dbg(&client->dev, "%s: packets: %d\n", __func__, packet_num);
+
+	msgs = kmalloc((packet_num + 1)*sizeof(struct i2c_msg), GFP_KERNEL);
+	if (!msgs) {
+		r = -ENOMEM;
+		goto out;
+	}
+
+	i = 1;
+	for (n = 0; n <= packet_num; n++) {
+		len = ptr[i];
+
+		dev_dbg(&client->dev, "%s: len[%d]: %d\n",
+			__func__, n, len);
+
+		if (i + len + 1 <= fw_p->size) {
+			msgs[n].addr = client->addr;
+			msgs[n].flags = 0;
+			msgs[n].len = len;
+			msgs[n].buf = ptr + i + 1;
+		} else {
+			break;
+		}
+
+		i += len + 1;
+	}
+
+	r = i2c_transfer(client->adapter, msgs, packet_num);
+	kfree(msgs);
+
+	if (r != packet_num) {
+		dev_err(&client->dev, "FW upload error: %d\n", r);
+		dev_dbg(&client->dev, "%d != %d\n", packet_num, r);
+
+		r =  -EREMOTEIO;
+		goto out;
+	} else {
+		r = 0;
+	}
+
+	/* ignore possible error here */
+	wl1273_fm_write_cmd(core, WL1273_RESET, 0);
+	dev_dbg(&client->dev, "n: %d, i: %d\n", n, i);
+
+	if (n - 1  != packet_num)
+		dev_warn(&client->dev, "%s - incorrect firmware size.\n",
+			 __func__);
+
+	if (i != fw_p->size)
+		dev_warn(&client->dev, "%s - inconsistent firmware.\n",
+			 __func__);
+
+	dev_dbg(&client->dev, "%s - download OK, r: %d\n", __func__, r);
+
+out:
+	release_firmware(fw_p);
+	return r;
+}
+
+static int wl1273_fm_stop(struct wl1273_core *core)
+{
+	struct i2c_client *client = core->i2c_dev;
+	struct wl1273_fm_platform_data *pdata =
+		client->dev.platform_data;
+
+	if (core->mode == WL1273_MODE_RX) {
+		int r = wl1273_fm_write_cmd(core, WL1273_POWER_SET,
+					    WL1273_POWER_SET_OFF);
+		if (r)
+			dev_err(&core->i2c_dev->dev,
+				"%s: POWER_SET fails: %d\n", __func__, r);
+	} else if (core->mode == WL1273_MODE_TX) {
+		int r = wl1273_fm_write_cmd(core, WL1273_PUPD_SET,
+					    WL1273_PUPD_SET_OFF);
+		if (r)
+			dev_err(&core->i2c_dev->dev,
+				"%s: PUPD_SET fails: %d\n", __func__, r);
+	}
+
+	if (pdata->disable) {
+		pdata->disable();
+		dev_dbg(&core->i2c_dev->dev, "Back to reset\n");
+	}
+
+	return 0;
+}
+
+static int wl1273_fm_start(struct wl1273_core *core, int new_mode)
+{
+	struct i2c_client *client = core->i2c_dev;
+	struct wl1273_fm_platform_data *pdata =
+		client->dev.platform_data;
+	int r = -EINVAL;
+
+	if (pdata->enable && core->mode == WL1273_MODE_OFF) {
+		pdata->enable();
+		msleep(250);
+	}
+
+	if (new_mode == WL1273_MODE_RX) {
+		u16 val = WL1273_POWER_SET_FM;
+
+		if (core->rds_on)
+			val |= WL1273_POWER_SET_RDS;
+
+		/* If this fails try again */
+		r = wl1273_fm_write_cmd(core, WL1273_POWER_SET, val);
+		if (r) {
+			msleep(100);
+
+			r = wl1273_fm_write_cmd(core, WL1273_POWER_SET, val);
+			if (r) {
+				dev_err(&client->dev, "%s: POWER_SET fails.\n",
+					__func__);
+				goto fail;
+			}
+		}
+
+		/* rds buffer configuration */
+		core->wr_index = 0;
+		core->rd_index = 0;
+
+	} else if (new_mode == WL1273_MODE_TX) {
+		/* If this fails try again */
+		r = wl1273_fm_write_cmd(core, WL1273_PUPD_SET,
+					WL1273_PUPD_SET_ON);
+		if (r) {
+			msleep(100);
+			r = wl1273_fm_write_cmd(core, WL1273_PUPD_SET,
+						WL1273_PUPD_SET_ON);
+			if (r) {
+				dev_err(&client->dev, "%s: PUPD_SET fails.\n",
+					__func__);
+				goto fail;
+			}
+		}
+
+		if (core->rds_on)
+			r = wl1273_fm_write_cmd(core, WL1273_RDS_DATA_ENB, 1);
+		else
+			r = wl1273_fm_write_cmd(core, WL1273_RDS_DATA_ENB, 0);
+	} else {
+		dev_warn(&client->dev, "%s: Illegal mode.\n", __func__);
+	}
+
+	if (core->mode == WL1273_MODE_OFF) {
+		dev_dbg(&core->i2c_dev->dev, "Out of reset\n");
+
+		r = wl1273_fm_upload_firmware_patch(core);
+		if (r)
+			dev_warn(&client->dev, "Firmware upload failed.\n");
+	}
+
+	return 0;
+fail:
+	if (pdata->disable)
+		pdata->disable();
+
+	dev_dbg(&client->dev, "%s: return: %d\n", __func__, r);
+	return r;
+}
+
+static int wl1273_fm_suspend(struct wl1273_core *core)
+{
+	int r = 0;
+	struct i2c_client *client = core->i2c_dev;
+
+	/* Cannot go from OFF to SUSPENDED */
+	if (core->mode == WL1273_MODE_RX)
+		r = wl1273_fm_write_cmd(core, WL1273_POWER_SET,
+					WL1273_POWER_SET_RETENTION);
+	else if (core->mode == WL1273_MODE_TX)
+		r = wl1273_fm_write_cmd(core, WL1273_PUPD_SET,
+					WL1273_PUPD_SET_RETENTION);
+	else
+		r = -EINVAL;
+
+	if (r) {
+		dev_err(&client->dev, "%s: POWER_SET fails: %d\n", __func__, r);
+		goto out;
+	}
+
+out:
+	return r;
+}
+
+static int wl1273_fm_set_mode(struct wl1273_core *core, int mode)
+{
+	int r;
+	int old_mode;
+
+	dev_dbg(&core->i2c_dev->dev, "%s\n", __func__);
+	dev_dbg(&core->i2c_dev->dev, "Forbidden modes: 0x%02x\n",
+		core->forbidden);
+
+	old_mode = core->mode;
+	if (mode & core->forbidden) {
+		r = -EPERM;
+		goto out;
+	}
+
+	switch (mode) {
+	case WL1273_MODE_RX:
+	case WL1273_MODE_TX:
+		r = wl1273_fm_start(core, mode);
+		if (r) {
+			dev_err(&core->i2c_dev->dev, "%s: Cannot start.\n",
+				__func__);
+			wl1273_fm_stop(core);
+			goto out;
+		}
+
+		core->mode = mode;
+		r = wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET,
+					core->irq_flags);
+		if (r) {
+			dev_err(&core->i2c_dev->dev, "INT_MASK_SET fails.\n");
+			goto out;
+		}
+
+		/* remember previous settings */
+		if (mode == WL1273_MODE_RX) {
+			r = wl1273_fm_set_rx_freq(core, core->rx_frequency);
+			if (r) {
+				dev_err(&core->i2c_dev->dev,
+					"set freq fails: %d.\n", r);
+				goto out;
+			}
+
+			r = wl1273_fm_set_volume(core, core->volume);
+			if (r) {
+				dev_err(&core->i2c_dev->dev,
+					"set volume fails: %d.\n", r);
+				goto out;
+			}
+
+			dev_dbg(&core->i2c_dev->dev, "%s: Set vol: %d.\n",
+				__func__, core->volume);
+		} else {
+			r = wl1273_fm_set_tx_freq(core, core->tx_frequency);
+			if (r) {
+				dev_err(&core->i2c_dev->dev,
+					"set freq fails: %d.\n", r);
+				goto out;
+			}
+		}
+
+		dev_dbg(&core->i2c_dev->dev, "%s: Set audio mode.\n", __func__);
+
+		r = wl1273_fm_set_audio(core, core->audio_mode);
+		if (r)
+			dev_err(&core->i2c_dev->dev,
+				"Cannot set audio mode.\n");
+		break;
+
+	case WL1273_MODE_OFF:
+		r = wl1273_fm_stop(core);
+		if (r)
+			dev_err(&core->i2c_dev->dev,
+				"%s: Off fails: %d\n", __func__, r);
+		else
+			core->mode = WL1273_MODE_OFF;
+
+		break;
+
+	case WL1273_MODE_SUSPENDED:
+		r = wl1273_fm_suspend(core);
+		if (r)
+			dev_err(&core->i2c_dev->dev,
+				"%s: Suspend fails: %d\n", __func__, r);
+		else
+			core->mode = WL1273_MODE_SUSPENDED;
+
+		break;
+
+	default:
+		dev_err(&core->i2c_dev->dev, "%s: Unknown mode: %d\n",
+			__func__, mode);
+		r = -EINVAL;
+		break;
+	}
+
+out:
+	if (r)
+		core->mode = old_mode ;
+
+	return r;
+}
+
+static int wl1273_fm_set_seek(struct wl1273_core *core,
+			      unsigned int wrap_around,
+			      unsigned int seek_upward,
+			      int level)
+{
+	int r = 0;
+	unsigned int dir = (seek_upward == 0) ? 0 : 1;
+	unsigned int rx_frequency, top_frequency, bottom_frequency;
+
+	rx_frequency = core->rx_frequency;
+	top_frequency = core->bands[core->band].top_frequency;
+	bottom_frequency = core->bands[core->band].bottom_frequency;
+	dev_dbg(&core->i2c_dev->dev, "core->rx_frequency: %d\n",
+		rx_frequency);
+
+	if (dir && rx_frequency + core->spacing <= top_frequency)
+		r = wl1273_fm_set_rx_freq(core, rx_frequency + core->spacing);
+	else if (dir && wrap_around)
+		r = wl1273_fm_set_rx_freq(core, bottom_frequency);
+	else if (rx_frequency - core->spacing >= bottom_frequency)
+		r = wl1273_fm_set_rx_freq(core, rx_frequency - core->spacing);
+	else if (wrap_around)
+		r = wl1273_fm_set_rx_freq(core, top_frequency);
+
+	if (r)
+		goto out;
+
+	if (level < SCHAR_MIN || level > SCHAR_MAX)
+		return -EINVAL;
+
+	INIT_COMPLETION(core->busy);
+	dev_dbg(&core->i2c_dev->dev, "%s: BUSY\n", __func__);
+
+	r = wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET, core->irq_flags);
+	if (r)
+		goto out;
+
+	dev_dbg(&core->i2c_dev->dev, "%s\n", __func__);
+
+	r = wl1273_fm_write_cmd(core, WL1273_SEARCH_LVL_SET, level);
+	if (r)
+		goto out;
+
+	r = wl1273_fm_write_cmd(core, WL1273_SEARCH_DIR_SET, dir);
+	if (r)
+		goto out;
+
+	r = wl1273_fm_write_cmd(core, WL1273_TUNER_MODE_SET,
+				TUNER_MODE_AUTO_SEEK);
+	if (r)
+		goto out;
+
+	wait_for_completion_timeout(&core->busy, msecs_to_jiffies(1000));
+	if (!(core->irq_received & WL1273_BL_EVENT))
+		goto out;
+
+	core->irq_received &= ~WL1273_BL_EVENT;
+
+	if (!wrap_around)
+		goto out;
+
+	/* Wrap around */
+	dev_dbg(&core->i2c_dev->dev, "Wrap around in HW seek.\n");
+
+	if (seek_upward)
+		rx_frequency = bottom_frequency;
+	else
+		rx_frequency = top_frequency;
+
+	r = wl1273_fm_set_rx_freq(core, rx_frequency);
+	if (r)
+		goto out;
+
+	INIT_COMPLETION(core->busy);
+	dev_dbg(&core->i2c_dev->dev, "%s: BUSY\n", __func__);
+
+	r = wl1273_fm_write_cmd(core, WL1273_TUNER_MODE_SET,
+				TUNER_MODE_AUTO_SEEK);
+	if (r)
+		goto out;
+
+	wait_for_completion_timeout(&core->busy, msecs_to_jiffies(1000));
+out:
+	dev_dbg(&core->i2c_dev->dev, "%s: Err: %d\n", __func__, r);
+	return r;
+}
+
+/**
+ * wl1273_fm_set_band() -	Change the current band.
+ * @core:			A pointer to the device structure.
+ * @band:			The ID of the new band.
+ *
+ * Wl1273 supports only two bands USA/Europe and Japan.
+ */
+static int wl1273_fm_set_band(struct wl1273_core *core, unsigned int band)
+{
+	int r = 0;
+	unsigned int new_frequency = 0;
+
+	dev_err(&core->i2c_dev->dev, "%s: number of resion: %d\n", __func__,
+		core->number_of_bands);
+
+	if (band == core->band)
+		return 0;
+
+	if (core->mode == WL1273_MODE_OFF ||
+	    core->mode == WL1273_MODE_SUSPENDED)
+		return -EPERM;
+
+	if (band >= core->number_of_bands)
+		return -EINVAL;
+
+	mutex_lock(&core->lock);
+
+	core->band = band;
+
+	if (core->rx_frequency < core->bands[core->band].bottom_frequency)
+		new_frequency = core->bands[core->band].bottom_frequency;
+	else if (core->rx_frequency > core->bands[core->band].top_frequency)
+		new_frequency = core->bands[core->band].top_frequency;
+
+	if (new_frequency) {
+		core->rx_frequency = new_frequency;
+		if (core->mode == WL1273_MODE_RX) {
+			r = wl1273_fm_set_rx_freq(core, new_frequency);
+			if (r)
+				goto out;
+		}
+	}
+
+	new_frequency = 0;
+	if (core->tx_frequency < core->bands[core->band].bottom_frequency)
+		new_frequency = core->bands[core->band].bottom_frequency;
+	else if (core->tx_frequency > core->bands[core->band].top_frequency)
+		new_frequency = core->bands[core->band].top_frequency;
+
+	if (new_frequency) {
+		core->tx_frequency = new_frequency;
+
+		if (core->mode == WL1273_MODE_TX) {
+			r = wl1273_fm_set_tx_freq(core, new_frequency);
+			if (r)
+				goto out;
+		}
+	}
+
+out:
+	mutex_unlock(&core->lock);
+	return r;
+}
+
+/**
+ * wl1273_fm_get_tx_ctune() -	Get the TX tuning capacitor value.
+ * @core:			A pointer to the device struct.
+ */
+static unsigned int wl1273_fm_get_tx_ctune(struct wl1273_core *core)
+{
+	struct i2c_client *client = core->i2c_dev;
+	u16 val;
+	int r;
+
+	if (core->mode == WL1273_MODE_OFF ||
+	    core->mode == WL1273_MODE_SUSPENDED)
+		return -EPERM;
+
+	r = wl1273_fm_read_reg(core, WL1273_READ_FMANT_TUNE_VALUE, &val);
+	if (r) {
+		dev_err(&client->dev, "%s: I2C error: %d\n", __func__, r);
+		goto out;
+	}
+
+out:
+	return val;
+}
+
+/**
+ * wl1273_fm_set_preemphasis() - Set the TX pre-emphasis value.
+ * @core:			 A pointer to the device struct.
+ * @preemphasis:		 The new pre-amphasis value.
+ *
+ * Possible pre-emphasis values are: V4L2_PREEMPHASIS_DISABLED,
+ * V4L2_PREEMPHASIS_50_uS and V4L2_PREEMPHASIS_75_uS.
+ */
+static int wl1273_fm_set_preemphasis(struct wl1273_core *core,
+				     unsigned int preemphasis)
+{
+	int r;
+	u16 em;
+
+	if (core->mode == WL1273_MODE_OFF ||
+	    core->mode == WL1273_MODE_SUSPENDED)
+		return -EPERM;
+
+	mutex_lock(&core->lock);
+
+	switch (preemphasis) {
+	case V4L2_PREEMPHASIS_DISABLED:
+		em = 1;
+		break;
+	case V4L2_PREEMPHASIS_50_uS:
+		em = 0;
+		break;
+	case V4L2_PREEMPHASIS_75_uS:
+		em = 2;
+		break;
+	default:
+		r = -EINVAL;
+		goto out;
+	}
+
+	r = wl1273_fm_write_cmd(core, WL1273_PREMPH_SET, em);
+	if (r)
+		goto out;
+
+	core->preemphasis = preemphasis;
+
+out:
+	mutex_unlock(&core->lock);
+	return r;
+}
+
+static int wl1273_fm_rds_on(struct wl1273_core *core)
+{
+	int r;
+
+	dev_dbg(&core->i2c_dev->dev, "%s\n", __func__);
+	if (core->rds_on)
+		return 0;
+
+	r = wl1273_fm_write_cmd(core, WL1273_POWER_SET,
+				WL1273_POWER_SET_FM | WL1273_POWER_SET_RDS);
+	if (r)
+		goto out;
+
+	r = wl1273_fm_set_rx_freq(core, core->rx_frequency);
+	if (r)
+		dev_err(&core->i2c_dev->dev, "set freq fails: %d.\n", r);
+out:
+	return r;
+}
+
+static int wl1273_fm_rds_off(struct wl1273_core *core)
+{
+	struct device *dev = &core->i2c_dev->dev;
+	int r;
+
+	if (!core->rds_on)
+		return 0;
+
+	core->irq_flags &= ~WL1273_RDS_EVENT;
+
+	r = wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET, core->irq_flags);
+	if (r)
+		goto out;
+
+	/* stop rds reception */
+	cancel_delayed_work(&core->work);
+
+	/* Service pending read */
+	wake_up_interruptible(&core->read_queue);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	r = wl1273_fm_write_cmd(core, WL1273_POWER_SET, WL1273_POWER_SET_FM);
+	if (r)
+		goto out;
+
+	r = wl1273_fm_set_rx_freq(core, core->rx_frequency);
+	if (r)
+		dev_err(&core->i2c_dev->dev, "set freq fails: %d.\n", r);
+
+out:
+	dev_dbg(dev, "%s: exiting...\n", __func__);
+
+	return r;
+}
+
+static int wl1273_fm_set_rds(struct wl1273_core *core, unsigned int new_mode)
+{
+	int r = 0;
+	struct i2c_client *client = core->i2c_dev;
+
+	if (core->mode == WL1273_MODE_OFF ||
+	    core->mode == WL1273_MODE_SUSPENDED)
+		return -EPERM;
+
+	if (new_mode == WL1273_RDS_RESET) {
+		r = wl1273_fm_write_cmd(core, WL1273_RDS_CNTRL_SET, 1);
+		return r;
+	}
+
+	if (core->mode == WL1273_MODE_TX && new_mode == WL1273_RDS_OFF) {
+		r = wl1273_fm_write_cmd(core, WL1273_RDS_DATA_ENB, 0);
+	} else if (core->mode == WL1273_MODE_TX && new_mode == WL1273_RDS_ON) {
+		r = wl1273_fm_write_cmd(core, WL1273_RDS_DATA_ENB, 1);
+	} else if (core->mode == WL1273_MODE_RX && new_mode == WL1273_RDS_OFF) {
+		r = wl1273_fm_rds_off(core);
+	} else if (core->mode == WL1273_MODE_RX && new_mode == WL1273_RDS_ON) {
+		r = wl1273_fm_rds_on(core);
+	} else {
+		dev_err(&client->dev, "%s: Unknown mode: %d\n", __func__,
+			new_mode);
+		r = -EINVAL;
+	}
+
+	if (!r)
+		core->rds_on = (new_mode == WL1273_RDS_ON) ? true : false;
+
+	return r;
+}
+
+static ssize_t wl1273_fm_fops_write(struct file *file, const char __user *buf,
+				    size_t count, loff_t *ppos)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	u16 val;
+	int r;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (radio->core->mode != WL1273_MODE_TX)
+		return count;
+
+	if (!radio->rds_on) {
+		dev_warn(radio->dev, "%s: RDS not on.\n", __func__);
+		return 0;
+	}
+
+	if (mutex_lock_interruptible(&radio->core->lock))
+		return -EINTR;
+
+	/*
+	 * Multiple processes can open the device, but only
+	 * one gets to write to it.
+	 */
+	if (radio->owner && radio->owner != file) {
+		r = -EBUSY;
+		goto out;
+	}
+	radio->owner = file;
+
+	/* Manual Mode */
+	if (count > 255)
+		val = 255;
+	else
+		val = count;
+
+	wl1273_fm_write_cmd(radio->core, WL1273_RDS_CONFIG_DATA_SET, val);
+
+	if (copy_from_user(radio->write_buf + 1, buf, val)) {
+		r = -EFAULT;
+		goto out;
+	}
+
+	dev_dbg(radio->dev, "Count: %d\n", val);
+	dev_dbg(radio->dev, "From user: \"%s\"\n", radio->write_buf);
+
+	radio->write_buf[0] = WL1273_RDS_DATA_SET;
+	wl1273_fm_write_data(radio->core, radio->write_buf, val + 1);
+
+	r = val;
+out:
+	mutex_unlock(&radio->core->lock);
+
+	return r;
+}
+
+static unsigned int wl1273_fm_fops_poll(struct file *file,
+					struct poll_table_struct *pts)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	unsigned int rd_index, wr_index;
+
+	if (radio->owner && radio->owner != file)
+		return -EBUSY;
+
+	radio->owner = file;
+	poll_wait(file, &core->read_queue, pts);
+
+	rd_index = core->rd_index;
+	wr_index = core->wr_index;
+	if (rd_index != wr_index)
+		return POLLIN | POLLOUT | POLLRDNORM;
+
+	return 0;
+}
+
+static int wl1273_fm_fops_open(struct file *file)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r = 0;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (core->mode == WL1273_MODE_RX && core->rds_on && !radio->rds_on) {
+		dev_dbg(radio->dev, "%s: Mode: %d\n", __func__, core->mode);
+
+		if (mutex_lock_interruptible(&core->lock))
+			return -EINTR;
+
+		core->irq_flags |= WL1273_RDS_EVENT;
+
+		r = wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET,
+					core->irq_flags);
+		if (r) {
+			mutex_unlock(&core->lock);
+			goto out;
+		}
+
+		radio->rds_on = true;
+		mutex_unlock(&core->lock);
+	}
+out:
+	return r;
+}
+
+static int wl1273_fm_fops_release(struct file *file)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r = 0;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (radio->rds_on) {
+		if (mutex_lock_interruptible(&core->lock))
+			return -EINTR;
+
+		core->irq_flags &= ~WL1273_RDS_EVENT;
+
+		if (core->mode == WL1273_MODE_RX) {
+			r = wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET,
+						core->irq_flags);
+			if (r) {
+				mutex_unlock(&core->lock);
+				goto out;
+			}
+
+		radio->rds_on = false;
+		mutex_unlock(&core->lock);
+		}
+	}
+
+	if (file == radio->owner)
+		radio->owner = NULL;
+out:
+	return r;
+}
+
+static ssize_t wl1273_fm_fops_read(struct file *file, char __user *buf,
+				   size_t count, loff_t *ppos)
+{
+	int r = 0;
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	unsigned int block_count = 0;
+	u16 val;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (radio->core->mode != WL1273_MODE_RX)
+		return 0;
+
+	if (!radio->rds_on) {
+		dev_warn(radio->dev, "%s: RDS not on.\n", __func__);
+		return 0;
+	}
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	/*
+	 * Multiple processes can open the device, but only
+	 * one gets to read from it.
+	 */
+	if (radio->owner && radio->owner != file) {
+		r = -EBUSY;
+		goto out;
+	}
+	radio->owner = file;
+
+	r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
+	if (r) {
+		dev_err(radio->dev, "%s: Get RDS_SYNC fails.\n", __func__);
+		goto out;
+	} else if (val == 0) {
+		dev_info(radio->dev, "RDS_SYNC: Not synchronized\n");
+		r = -ENODATA;
+		goto out;
+	}
+
+	/* block if no new data available */
+	while (core->wr_index == core->rd_index) {
+		if (file->f_flags & O_NONBLOCK) {
+			r = -EWOULDBLOCK;
+			goto out;
+		}
+
+		dev_dbg(radio->dev, "%s: Wait for RDS data.\n", __func__);
+		if (wait_event_interruptible(core->read_queue,
+					     core->wr_index !=
+					     core->rd_index) < 0) {
+			r = -EINTR;
+			goto out;
+		}
+	}
+
+	/* calculate block count from byte count */
+	count /= 3;
+
+	/* copy RDS blocks from the internal buffer and to user buffer */
+	while (block_count < count) {
+		if (core->rd_index == core->wr_index)
+			break;
+
+		/* always transfer complete RDS blocks */
+		if (copy_to_user(buf, &core->buffer[core->rd_index], 3))
+			break;
+
+		/* increment and wrap the read pointer */
+		core->rd_index += 3;
+		if (core->rd_index >= core->buf_size)
+			core->rd_index = 0;
+
+		/* increment counters */
+		block_count++;
+		buf += 3;
+		r += 3;
+	}
+
+out:
+	dev_dbg(radio->dev, "%s: exit\n", __func__);
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static const struct v4l2_file_operations wl1273_fops = {
+	.owner		= THIS_MODULE,
+	.read		= wl1273_fm_fops_read,
+	.write		= wl1273_fm_fops_write,
+	.poll		= wl1273_fm_fops_poll,
+	.ioctl		= video_ioctl2,
+	.open		= wl1273_fm_fops_open,
+	.release	= wl1273_fm_fops_release,
+};
+
+static int wl1273_fm_vidioc_querycap(struct file *file, void *priv,
+				     struct v4l2_capability *capability)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	strlcpy(capability->driver, WL1273_FM_DRIVER_NAME,
+		sizeof(capability->driver));
+	strlcpy(capability->card, "Texas Instruments Wl1273 FM Radio",
+		sizeof(capability->card));
+	strlcpy(capability->bus_info, "I2C", sizeof(capability->bus_info));
+
+	capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
+		V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_AUDIO |
+		V4L2_CAP_RDS_CAPTURE | V4L2_CAP_MODULATOR |
+		V4L2_CAP_RDS_OUTPUT;
+
+	return 0;
+}
+
+static int wl1273_fm_vidioc_g_input(struct file *file, void *priv,
+				    unsigned int *i)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	*i = 0;
+
+	return 0;
+}
+
+static int wl1273_fm_vidioc_s_input(struct file *file, void *priv,
+				    unsigned int i)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (i != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int wl1273_fm_vidioc_queryctrl(struct file *file, void *priv,
+				      struct v4l2_queryctrl *qc)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	switch (qc->id) {
+	case V4L2_CID_AUDIO_VOLUME:
+		return v4l2_ctrl_query_fill(qc, 0, WL1273_MAX_VOLUME, 1,
+					    WL1273_DEFAULT_VOLUME);
+	case  V4L2_CID_AUDIO_MUTE:
+		return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
+
+	case V4L2_CID_TUNE_PREEMPHASIS:
+		return v4l2_ctrl_query_fill(qc, V4L2_PREEMPHASIS_DISABLED,
+					    V4L2_PREEMPHASIS_75_uS, 1,
+					    V4L2_PREEMPHASIS_50_uS);
+	case V4L2_CID_TUNE_POWER_LEVEL:
+		return v4l2_ctrl_query_fill(qc, 0, 37, 1, 4);
+
+	case V4L2_CID_FM_RX_BAND:
+		return v4l2_ctrl_query_fill(qc, V4L2_FM_BAND_OTHER,
+					    V4L2_FM_BAND_JAPAN, 1,
+					    V4L2_FM_BAND_OTHER);
+	}
+	return -EINVAL;
+}
+
+static int wl1273_fm_vidioc_querymenu(struct file *file, void *priv,
+				      struct v4l2_querymenu *qm)
+{
+	static u32 fm_rx_bands[] = {
+		 V4L2_FM_BAND_OTHER,
+		 V4L2_FM_BAND_JAPAN,
+		 V4L2_CTRL_MENU_IDS_END
+	};
+	struct v4l2_queryctrl qctrl;
+	int r;
+
+	qctrl.id = qm->id;
+	r = wl1273_fm_vidioc_queryctrl(file, priv, &qctrl);
+	if (r)
+		return r;
+
+	switch (qm->id) {
+	case V4L2_CID_FM_RX_BAND:
+		return v4l2_ctrl_query_menu_valid_items(qm, fm_rx_bands);
+	}
+
+	return v4l2_ctrl_query_menu(qm, &qctrl, NULL);
+}
+
+/**
+ * wl1273_fm_set_tx_power() - 	Set the transmission power value.
+ * @core:			A pointer to the device struct.
+ * @power:			The new power value.
+ */
+static int wl1273_fm_set_tx_power(struct wl1273_core *core, u16 power)
+{
+	int r;
+
+	if (core->mode == WL1273_MODE_OFF ||
+	    core->mode == WL1273_MODE_SUSPENDED)
+		return -EPERM;
+
+	mutex_lock(&core->lock);
+
+	r = wl1273_fm_write_cmd(core, WL1273_POWER_LEV_SET, power);
+	if (r)
+		goto out;
+
+	core->tx_power = power;
+
+out:
+	mutex_unlock(&core->lock);
+	return r;
+}
+
+#define WL1273_SPACING_50kHz	1
+#define WL1273_SPACING_100kHz	2
+#define WL1273_SPACING_200kHz	4
+
+static int wl1273_fm_tx_set_spacing(struct wl1273_core *core,
+				    unsigned int spacing)
+{
+	int r;
+
+	if (spacing == 0) {
+		r = wl1273_fm_write_cmd(core, WL1273_SCAN_SPACING_SET,
+					WL1273_SPACING_100kHz);
+		core->spacing = 100;
+	} else if (spacing - 50 < 25) {
+		r = wl1273_fm_write_cmd(core, WL1273_SCAN_SPACING_SET,
+					WL1273_SPACING_50kHz);
+		core->spacing = 50;
+	} else if (spacing - 100 < 50) {
+		r = wl1273_fm_write_cmd(core, WL1273_SCAN_SPACING_SET,
+					WL1273_SPACING_100kHz);
+		core->spacing = 100;
+	} else {
+		r = wl1273_fm_write_cmd(core, WL1273_SCAN_SPACING_SET,
+					WL1273_SPACING_200kHz);
+		core->spacing = 200;
+	}
+
+	return r;
+}
+
+static int wl1273_fm_vidioc_g_ctrl(struct file *file, void *priv,
+				   struct v4l2_control *ctrl)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	u16 val;
+	int r = 0;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (core->mode == WL1273_MODE_RX)
+			r = wl1273_fm_read_reg(core, WL1273_MUTE_STATUS_SET,
+					       &val);
+		else
+			r = wl1273_fm_read_reg(core, WL1273_MUTE, &val);
+
+		if (r)
+			goto out;
+
+		dev_dbg(radio->dev,
+			"MUTE STATUS GET: 0x%02x\n", val);
+
+		if (val)
+			ctrl->value = 1;
+		else
+			ctrl->value = 0;
+
+		break;
+
+	case V4L2_CID_AUDIO_VOLUME:
+		ctrl->value = core->volume;
+		break;
+
+	case  V4L2_CID_TUNE_ANTENNA_CAPACITOR:
+		ctrl->value = wl1273_fm_get_tx_ctune(core);
+		break;
+
+	case V4L2_CID_TUNE_PREEMPHASIS:
+		ctrl->value = core->preemphasis;
+		break;
+
+	case V4L2_CID_TUNE_POWER_LEVEL:
+		ctrl->value = core->tx_power;
+		break;
+
+	case V4L2_CID_FM_RX_BAND:
+		ctrl->value = core->band;
+		break;
+
+	default:
+		dev_warn(radio->dev, "%s: Unknown IOCTL: %d\n",
+			 __func__, ctrl->id);
+		break;
+	}
+
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+#define WL1273_MUTE_SOFT_ENABLE    (1 << 0)
+#define WL1273_MUTE_AC             (1 << 1)
+#define WL1273_MUTE_HARD_LEFT      (1 << 2)
+#define WL1273_MUTE_HARD_RIGHT     (1 << 3)
+#define WL1273_MUTE_SOFT_FORCE     (1 << 4)
+
+static int wl1273_fm_vidioc_s_ctrl(struct file *file, void *priv,
+				   struct v4l2_control *ctrl)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r = 0;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (mutex_lock_interruptible(&core->lock))
+			return -EINTR;
+
+		if (core->mode == WL1273_MODE_RX && ctrl->value)
+			r = wl1273_fm_write_cmd(core,
+						WL1273_MUTE_STATUS_SET,
+						WL1273_MUTE_HARD_LEFT |
+						WL1273_MUTE_HARD_RIGHT);
+		else if (core->mode == WL1273_MODE_RX)
+			r = wl1273_fm_write_cmd(core,
+						WL1273_MUTE_STATUS_SET, 0x0);
+		else if (core->mode == WL1273_MODE_TX && ctrl->value)
+			r = wl1273_fm_write_cmd(core, WL1273_MUTE, 1);
+		else if (core->mode == WL1273_MODE_TX)
+			r = wl1273_fm_write_cmd(core, WL1273_MUTE, 0);
+
+		mutex_unlock(&core->lock);
+		break;
+
+	case V4L2_CID_AUDIO_VOLUME:
+		if (ctrl->value == 0)
+			r = wl1273_fm_set_mode(core, WL1273_MODE_OFF);
+		else
+			r =  wl1273_fm_set_volume(core, core->volume);
+		break;
+
+	case V4L2_CID_TUNE_PREEMPHASIS:
+		r = wl1273_fm_set_preemphasis(core, ctrl->value);
+		break;
+
+	case V4L2_CID_TUNE_POWER_LEVEL:
+		r = wl1273_fm_set_tx_power(core, ctrl->value);
+		break;
+
+	case V4L2_CID_FM_RX_BAND:
+		r = wl1273_fm_set_band(core, ctrl->value);
+		break;
+
+	default:
+		dev_warn(radio->dev, "%s: Unknown IOCTL: %d\n",
+			 __func__, ctrl->id);
+		break;
+	}
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+	return r;
+}
+
+static int wl1273_fm_vidioc_g_audio(struct file *file, void *priv,
+				    struct v4l2_audio *audio)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (audio->index > 1)
+		return -EINVAL;
+
+	strlcpy(audio->name, "Radio", sizeof(audio->name));
+	audio->capability = V4L2_AUDCAP_STEREO;
+
+	return 0;
+}
+
+static int wl1273_fm_vidioc_s_audio(struct file *file, void *priv,
+				    struct v4l2_audio *audio)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (audio->index != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+#define WL1273_RDS_NOT_SYNCHRONIZED 0
+#define WL1273_RDS_SYNCHRONIZED 1
+
+static int wl1273_fm_vidioc_g_tuner(struct file *file, void *priv,
+				    struct v4l2_tuner *tuner)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	u16 val;
+	int r;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (tuner->index > 0)
+		return -EINVAL;
+
+	strcpy(tuner->name, WL1273_FM_DRIVER_NAME);
+	tuner->type = V4L2_TUNER_RADIO;
+
+	tuner->rangelow	=
+		WL1273_FREQ(core->bands[core->band].bottom_frequency);
+	tuner->rangehigh =
+		WL1273_FREQ(core->bands[core->band].top_frequency);
+
+	tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+	tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_RDS;
+
+	if (core->mode != WL1273_MODE_RX)
+		return 0;
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	r = wl1273_fm_read_reg(core, WL1273_RSSI_LVL_GET, &val);
+	if (r)
+		goto out;
+
+	tuner->signal = (s16) val;
+	dev_dbg(radio->dev, "Signal: %d\n", tuner->signal);
+
+	tuner->afc = 0;
+
+	r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
+	if (r)
+		goto out;
+
+	if (val == WL1273_RDS_SYNCHRONIZED)
+		tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int wl1273_fm_vidioc_s_tuner(struct file *file, void *priv,
+				    struct v4l2_tuner *tuner)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r = 0;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+	dev_dbg(radio->dev, "tuner->index: %d\n", tuner->index);
+	dev_dbg(radio->dev, "tuner->name: %s\n", tuner->name);
+	dev_dbg(radio->dev, "tuner->capability: 0x%04x\n", tuner->capability);
+	dev_dbg(radio->dev, "tuner->rxsubchans: 0x%04x\n", tuner->rxsubchans);
+	dev_dbg(radio->dev, "tuner->rangelow: %d\n", tuner->rangelow);
+	dev_dbg(radio->dev, "tuner->rangehigh: %d\n", tuner->rangehigh);
+
+	if (tuner->index > 0)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	r = wl1273_fm_set_mode(core, WL1273_MODE_RX);
+	if (r)
+		goto out;
+
+	if (tuner->rxsubchans & V4L2_TUNER_SUB_RDS)
+		r = wl1273_fm_set_rds(core, WL1273_RDS_ON);
+	else
+		r = wl1273_fm_set_rds(core, WL1273_RDS_OFF);
+
+	if (r)
+		dev_warn(radio->dev, "%s: RDS fails: %d\n", __func__, r);
+
+	if (tuner->audmode == V4L2_TUNER_MODE_MONO)
+		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
+					WL1273_RX_MONO);
+	else
+		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
+					WL1273_RX_STEREO);
+	if (r < 0)
+		dev_warn(radio->dev, WL1273_FM_DRIVER_NAME
+			 ": set tuner mode failed with %d\n", r);
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int wl1273_fm_vidioc_g_frequency(struct file *file, void *priv,
+					struct v4l2_frequency *freq)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	freq->type = V4L2_TUNER_RADIO;
+	freq->frequency = WL1273_FREQ(wl1273_fm_get_freq(core));
+
+	mutex_unlock(&core->lock);
+
+	return 0;
+}
+
+static int wl1273_fm_vidioc_s_frequency(struct file *file, void *priv,
+					struct v4l2_frequency *freq)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r;
+
+	dev_dbg(radio->dev, "%s: %d\n", __func__, freq->frequency);
+
+	if (freq->type != V4L2_TUNER_RADIO) {
+		dev_dbg(radio->dev,
+			"freq->type != V4L2_TUNER_RADIO: %d\n", freq->type);
+		return -EINVAL;
+	}
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	if (core->mode == WL1273_MODE_RX) {
+		dev_dbg(radio->dev, "freq: %d\n", freq->frequency);
+
+		r = wl1273_fm_set_rx_freq(core,
+					  WL1273_INV_FREQ(freq->frequency));
+		if (r)
+			dev_warn(radio->dev, WL1273_FM_DRIVER_NAME
+				 ": set frequency failed with %d\n", r);
+	} else {
+		r = wl1273_fm_set_tx_freq(core,
+					  WL1273_INV_FREQ(freq->frequency));
+		if (r)
+			dev_warn(radio->dev, WL1273_FM_DRIVER_NAME
+				 ": set frequency failed with %d\n", r);
+	}
+
+	mutex_unlock(&core->lock);
+
+	dev_dbg(radio->dev, "wl1273_vidioc_s_frequency: DONE\n");
+	return r;
+}
+
+#define WL1273_DEFAULT_SEEK_LEVEL	7
+
+static int wl1273_fm_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+					   struct v4l2_hw_freq_seek *seek)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (seek->tuner != 0 || seek->type != V4L2_TUNER_RADIO)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	r = wl1273_fm_set_mode(core, WL1273_MODE_RX);
+	if (r)
+		goto out;
+
+	r = wl1273_fm_tx_set_spacing(core, seek->spacing);
+	if (r)
+		dev_warn(radio->dev, "HW seek failed: %d\n", r);
+
+	r = wl1273_fm_set_seek(core, seek->wrap_around, seek->seek_upward,
+			       WL1273_DEFAULT_SEEK_LEVEL);
+	if (r)
+		dev_warn(radio->dev, "HW seek failed: %d\n", r);
+
+	mutex_unlock(&core->lock);
+ out:
+	return r;
+}
+
+static int wl1273_fm_vidioc_s_modulator(struct file *file, void *priv,
+					struct v4l2_modulator *modulator)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	int r = 0;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (modulator->index > 0)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	r = wl1273_fm_set_mode(core, WL1273_MODE_TX);
+	if (r)
+		goto out;
+
+	if (modulator->txsubchans & V4L2_TUNER_SUB_RDS)
+		r = wl1273_fm_set_rds(core, WL1273_RDS_ON);
+	else
+		r = wl1273_fm_set_rds(core, WL1273_RDS_OFF);
+
+out:
+	mutex_unlock(&core->lock);
+
+	return r;
+}
+
+static int wl1273_fm_vidioc_g_modulator(struct file *file, void *priv,
+					struct v4l2_modulator *modulator)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+
+	dev_dbg(radio->dev, "%s\n", __func__);
+
+	if (mutex_lock_interruptible(&core->lock))
+		return -EINTR;
+
+	strcpy(modulator->name, WL1273_FM_DRIVER_NAME);
+
+	modulator->rangelow =
+		WL1273_FREQ(core->bands[core->band].bottom_frequency);
+	modulator->rangehigh =
+		WL1273_FREQ(core->bands[core->band].top_frequency);
+
+	modulator->capability = V4L2_TUNER_CAP_RDS;
+
+	if (core->rds_on)
+		modulator->txsubchans |= V4L2_TUNER_SUB_RDS;
+	else
+		modulator->txsubchans &= ~V4L2_TUNER_SUB_RDS;
+
+	mutex_unlock(&core->lock);
+
+	return 0;
+}
+
+static int wl1273_fm_vidioc_log_status(struct file *file, void *priv)
+{
+	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
+	struct wl1273_core *core = radio->core;
+	u16 val;
+	int r;
+
+	dev_info(radio->dev, DRIVER_DESC);
+
+	if (core->mode == WL1273_MODE_OFF) {
+		dev_info(radio->dev, "Mode: Off\n");
+		return 0;
+	}
+
+	if (core->mode == WL1273_MODE_SUSPENDED) {
+		dev_info(radio->dev, "Mode: Suspended\n");
+		return 0;
+	}
+
+	r = wl1273_fm_read_reg(core, WL1273_ASIC_ID_GET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get ASIC_ID fails.\n", __func__);
+	else
+		dev_info(radio->dev, "ASIC_ID: 0x%04x\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_ASIC_VER_GET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get ASIC_VER fails.\n", __func__);
+	else
+		dev_info(radio->dev, "ASIC Version: 0x%04x\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_FIRM_VER_GET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get FIRM_VER fails.\n", __func__);
+	else
+		dev_info(radio->dev, "FW version: %d(0x%04x)\n", val, val);
+
+	/* TODO: Add TX stuff */
+	if (core->mode != WL1273_MODE_RX)
+		return 0;
+
+	r = wl1273_fm_read_reg(core, WL1273_FREQ_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get FREQ fails.\n", __func__);
+	else
+		dev_info(radio->dev, "RX Frequency: %dkHz\n",
+			core->bands[core->band].bottom_frequency + val*50);
+
+	r = wl1273_fm_read_reg(core, WL1273_MOST_MODE_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get MOST_MODE fails.\n",
+			__func__);
+	else if (val == 0)
+		dev_info(radio->dev,
+			 "MOST_MODE: Stereo output according to blend\n");
+	else if (val == 1)
+		dev_info(radio->dev, "MOST_MODE: Force mono output\n");
+	else
+		dev_info(radio->dev, "MOST_MODE: Unexpected value: %d\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_MOST_BLEND_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get MOST_BLEND fails.\n", __func__);
+	else if (val == 0)
+		dev_info(radio->dev,
+			 "MOST_BLEND: Switched blend with hysteresis.\n");
+	else if (val == 1)
+		dev_info(radio->dev, "MOST_BLEND: Soft blend.\n");
+	else
+		dev_info(radio->dev, "MOST_BLEND: Unexpected value: %d\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_STEREO_GET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get STEREO fails.\n", __func__);
+	else if (val == 0)
+		dev_info(radio->dev, "STEREO: Not detected\n");
+	else if (val == 1)
+		dev_info(radio->dev, "STEREO: Detected\n");
+	else
+		dev_info(radio->dev, "STEREO: Unexpected value: %d\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_RSSI_LVL_GET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get RSSI_LVL fails.\n", __func__);
+	else
+		dev_info(radio->dev, "RX signal strength: %d\n", (s16) val);
+
+	r = wl1273_fm_read_reg(core, WL1273_POWER_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get POWER fails.\n", __func__);
+	else
+		dev_info(radio->dev, "POWER: 0x%04x\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_INT_MASK_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get INT_MASK fails.\n", __func__);
+	else
+		dev_info(radio->dev, "INT_MASK: 0x%04x\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get RDS_SYNC fails.\n",
+			__func__);
+	else if (val == 0)
+		dev_info(radio->dev, "RDS_SYNC: Not synchronized\n");
+
+	else if (val == 1)
+		dev_info(radio->dev, "RDS_SYNC: Synchronized\n");
+	else
+		dev_info(radio->dev, "RDS_SYNC: Unexpected value: %d\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_I2S_MODE_CONFIG_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get I2S_MODE_CONFIG fails.\n",
+			__func__);
+	else
+		dev_info(radio->dev, "I2S_MODE_CONFIG: 0x%04x\n", val);
+
+	r = wl1273_fm_read_reg(core, WL1273_VOLUME_SET, &val);
+	if (r)
+		dev_err(radio->dev, "%s: Get VOLUME fails.\n",
+			__func__);
+	else
+		dev_info(radio->dev, "VOLUME: 0x%04x\n", val);
+
+	return 0;
+}
+
+static void wl1273_vdev_release(struct video_device *dev)
+{
+}
+
+static const struct v4l2_ioctl_ops wl1273_ioctl_ops = {
+	.vidioc_querycap	= wl1273_fm_vidioc_querycap,
+	.vidioc_g_input		= wl1273_fm_vidioc_g_input,
+	.vidioc_s_input		= wl1273_fm_vidioc_s_input,
+	.vidioc_queryctrl	= wl1273_fm_vidioc_queryctrl,
+	.vidioc_querymenu	= wl1273_fm_vidioc_querymenu,
+	.vidioc_g_ctrl		= wl1273_fm_vidioc_g_ctrl,
+	.vidioc_s_ctrl		= wl1273_fm_vidioc_s_ctrl,
+	.vidioc_g_audio		= wl1273_fm_vidioc_g_audio,
+	.vidioc_s_audio		= wl1273_fm_vidioc_s_audio,
+	.vidioc_g_tuner		= wl1273_fm_vidioc_g_tuner,
+	.vidioc_s_tuner		= wl1273_fm_vidioc_s_tuner,
+	.vidioc_g_frequency	= wl1273_fm_vidioc_g_frequency,
+	.vidioc_s_frequency	= wl1273_fm_vidioc_s_frequency,
+	.vidioc_s_hw_freq_seek	= wl1273_fm_vidioc_s_hw_freq_seek,
+	.vidioc_g_modulator	= wl1273_fm_vidioc_g_modulator,
+	.vidioc_s_modulator	= wl1273_fm_vidioc_s_modulator,
+	.vidioc_log_status      = wl1273_fm_vidioc_log_status,
+};
+
+static struct video_device wl1273_viddev_template = {
+	.fops			= &wl1273_fops,
+	.ioctl_ops		= &wl1273_ioctl_ops,
+	.name			= WL1273_FM_DRIVER_NAME,
+	.release		= wl1273_vdev_release,
+};
+
+static int wl1273_fm_radio_remove(struct platform_device *pdev)
+{
+	struct wl1273_device *radio = platform_get_drvdata(pdev);;
+
+	dev_info(&pdev->dev, "%s.\n", __func__);
+
+	video_unregister_device(&radio->videodev);
+	v4l2_device_unregister(&radio->v4l2dev);
+	kfree(radio->write_buf);
+	kfree(radio);
+
+	return 0;
+}
+
+static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev)
+{
+	struct wl1273_core **pdata = pdev->dev.platform_data;
+	struct wl1273_device *radio;
+	int r = 0;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data.\n");
+		return -EINVAL;
+	}
+
+	radio = kzalloc(sizeof(*radio), GFP_KERNEL);
+	if (!radio)
+		return -ENOMEM;
+
+	radio->write_buf = kmalloc(256, GFP_KERNEL);
+	if (!radio->write_buf)
+		return -ENOMEM;
+
+	radio->core = *pdata;
+	radio->dev = &pdev->dev;
+	radio->rds_on = false;
+
+	r = v4l2_device_register(&pdev->dev, &radio->v4l2dev);
+	if (r) {
+		dev_err(&pdev->dev, "Cannot register v4l2_device.\n");
+		goto err_device_alloc;
+	}
+
+	/* V4L2 configuration */
+	memcpy(&radio->videodev, &wl1273_viddev_template,
+	       sizeof(wl1273_viddev_template));
+
+	radio->videodev.v4l2_dev = &radio->v4l2dev;
+
+	/* register video device */
+	r = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr);
+	if (r) {
+		dev_err(&pdev->dev, WL1273_FM_DRIVER_NAME
+			": Could not register video device\n");
+		goto err_video_register;
+	}
+
+	video_set_drvdata(&radio->videodev, radio);
+	platform_set_drvdata(pdev, radio);
+
+	return 0;
+
+err_video_register:
+	v4l2_device_unregister(&radio->v4l2dev);
+err_device_alloc:
+	kfree(radio);
+
+	return r;
+}
+
+MODULE_ALIAS("platform:wl1273_fm_radio");
+
+static struct platform_driver wl1273_fm_radio_driver = {
+	.probe		= wl1273_fm_radio_probe,
+	.remove		= __devexit_p(wl1273_fm_radio_remove),
+	.driver		= {
+		.name	= "wl1273_fm_radio",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init wl1273_fm_module_init(void)
+{
+	pr_info("%s\n", __func__);
+	return platform_driver_register(&wl1273_fm_radio_driver);
+}
+module_init(wl1273_fm_module_init);
+
+static void __exit wl1273_fm_module_exit(void)
+{
+	flush_scheduled_work();
+	platform_driver_unregister(&wl1273_fm_radio_driver);
+	pr_info(DRIVER_DESC ", Exiting.\n");
+}
+module_exit(wl1273_fm_module_exit);
+
+MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 413576a..5998a94 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -135,6 +135,12 @@ config TWL4030_CODEC
 	select MFD_CORE
 	default n
 
+config WL1273_CORE
+	bool
+	depends on I2C
+	select MFD_CORE
+	default n
+
 config MFD_TMIO
 	bool
 	default n
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 78295d6..46e611d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -30,6 +30,8 @@ obj-$(CONFIG_TWL4030_CORE)	+= twl-core.o twl4030-irq.o twl6030-irq.o
 obj-$(CONFIG_TWL4030_POWER)    += twl4030-power.o
 obj-$(CONFIG_TWL4030_CODEC)	+= twl4030-codec.o
 
+obj-$(CONFIG_WL1273_CORE)	+= wl1273-core.o
+
 obj-$(CONFIG_MFD_MC13783)	+= mc13783-core.o
 
 obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
-- 
1.6.1.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v5 5/5] Documentation: v4l: Add hw_seek spacing and FM_RX class
  2010-07-16 10:27       ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
@ 2010-07-16 10:27         ` Matti J. Aaltonen
  2010-07-18  9:57           ` Hans Verkuil
  2010-07-18  9:52         ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Hans Verkuil
  1 sibling, 1 reply; 11+ messages in thread
From: Matti J. Aaltonen @ 2010-07-16 10:27 UTC (permalink / raw)
  To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen

Add a couple of words about the spacing field in ithe HW seek struct and
about the new FM RX control class.

Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
---
 Documentation/DocBook/v4l/controls.xml             |   71 ++++++++++++++++++++
 .../DocBook/v4l/vidioc-s-hw-freq-seek.xml          |   10 ++-
 2 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/Documentation/DocBook/v4l/controls.xml b/Documentation/DocBook/v4l/controls.xml
index e1bdbb6..9725f06 100644
--- a/Documentation/DocBook/v4l/controls.xml
+++ b/Documentation/DocBook/v4l/controls.xml
@@ -2062,6 +2062,77 @@ manually or automatically if set to zero. Unit, range and step are driver-specif
 <para>For more details about RDS specification, refer to
 <xref linkend="en50067" /> document, from CENELEC.</para>
     </section>
+    <section id="fm-rx-controls">
+      <title>FM Tuner Control Reference</title>
+
+      <para>The FM Tuner (FM_RX) class includes controls for common features of
+devices that are capable of receiving FM transmissions. Currently this class includes a parameter
+defining the FM radio band being used.</para>
+
+      <table pgwide="1" frame="none" id="fm-rx-control-id">
+      <title>FM_RX Control IDs</title>
+
+      <tgroup cols="4">
+	<colspec colname="c1" colwidth="1*" />
+	<colspec colname="c2" colwidth="6*" />
+	<colspec colname="c3" colwidth="2*" />
+	<colspec colname="c4" colwidth="6*" />
+	<spanspec namest="c1" nameend="c2" spanname="id" />
+	<spanspec namest="c2" nameend="c4" spanname="descr" />
+	<thead>
+	  <row>
+	    <entry spanname="id" align="left">ID</entry>
+	    <entry align="left">Type</entry>
+	  </row><row rowsep="1"><entry spanname="descr" align="left">Description</entry>
+	  </row>
+	</thead>
+	<tbody valign="top">
+	  <row><entry></entry></row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_FM_RX_CLASS</constant>&nbsp;</entry>
+	    <entry>class</entry>
+	  </row><row><entry spanname="descr">The FM_RX class
+descriptor. Calling &VIDIOC-QUERYCTRL; for this control will return a
+description of this control class.</entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_FM_RX_BAND</constant>&nbsp;</entry>
+	    <entry>integer</entry>
+	  </row>
+	  <row id="v4l2-fm_rx_band"><entry spanname="descr">Configures the FM radio band that is
+the frequency range being used. Currentrly there are three band in use, see  <ulink
+url="http://en.wikipedia.org/wiki/FM_broadcasting">Wikipedia</ulink>.
+Usually 87.5 to 108.0 MHz is used, or some portion thereof, with a few exceptions:
+In Japan, the band 76-90 MHz is used and
+In the former Soviet republics, and some former Eastern Bloc countries,
+the older 65-74 MHz band, referred also to as the OIRT band, is still used.
+
+The enum&nbsp; v4l2_fm_rx_band defines possible values for the FM band. They are:</entry>
+	</row><row>
+	<entrytbl spanname="descr" cols="2">
+		  <tbody valign="top">
+		    <row>
+		      <entry><constant>V4L2_FM_BAND_OTHER</constant>&nbsp;</entry>
+		      <entry>Frequencies from 87.5 to 108.0 MHz</entry>
+		    </row>
+		    <row>
+		      <entry><constant>V4L2_FM_BAND_JAPAN</constant>&nbsp;</entry>
+		      <entry>from 65 to 74 MHz</entry>
+		    </row>
+		    <row>
+		      <entry><constant>V4L2_FM_BAND_OIRT</constant>&nbsp;</entry>
+		      <entry>from 65 to 74 MHz</entry>
+		    </row>
+		  </tbody>
+		</entrytbl>
+
+	  </row>
+	  <row><entry></entry></row>
+	</tbody>
+      </tgroup>
+      </table>
+
+    </section>
 </section>
 
   <!--
diff --git a/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml b/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml
index 14b3ec7..8ee614c 100644
--- a/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml
+++ b/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml
@@ -51,7 +51,8 @@
 
     <para>Start a hardware frequency seek from the current frequency.
 To do this applications initialize the <structfield>tuner</structfield>,
-<structfield>type</structfield>, <structfield>seek_upward</structfield> and
+<structfield>type</structfield>, <structfield>seek_upward</structfield>,
+<structfield>spacing</structfield> and
 <structfield>wrap_around</structfield> fields, and zero out the
 <structfield>reserved</structfield> array of a &v4l2-hw-freq-seek; and
 call the <constant>VIDIOC_S_HW_FREQ_SEEK</constant> ioctl with a pointer
@@ -89,7 +90,12 @@ field and the &v4l2-tuner; <structfield>index</structfield> field.</entry>
 	  </row>
 	  <row>
 	    <entry>__u32</entry>
-	    <entry><structfield>reserved</structfield>[8]</entry>
+	    <entry><structfield>spacing</structfield></entry>
+	    <entry>If non-zero, gives the search resolution to be used in hardware scan. The driver selects the nearest value that is supported by the hardware. If spacing is zero use a reasonable default value.</entry>
+	  </row>
+	  <row>
+	    <entry>__u32</entry>
+	    <entry><structfield>reserved</structfield>[7]</entry>
 	    <entry>Reserved for future extensions. Drivers and
 	    applications must set the array to zero.</entry>
 	  </row>
-- 
1.6.1.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class.
  2010-07-16 10:27 ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
  2010-07-16 10:27   ` [PATCH v5 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
@ 2010-07-18  9:24   ` Hans Verkuil
  2010-07-18  9:55   ` Hans Verkuil
  2 siblings, 0 replies; 11+ messages in thread
From: Hans Verkuil @ 2010-07-18  9:24 UTC (permalink / raw)
  To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin

On Friday 16 July 2010 12:27:43 Matti J. Aaltonen wrote:
> Add spacing field to v4l2_hw_freq_seek and also add FM RX class to
> control classes.
> 
> Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
> ---
>  include/linux/videodev2.h |   15 ++++++++++++++-
>  1 files changed, 14 insertions(+), 1 deletions(-)
> 
> diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
> index 418dacf..95675cd 100644
> --- a/include/linux/videodev2.h
> +++ b/include/linux/videodev2.h
> @@ -935,6 +935,7 @@ struct v4l2_ext_controls {
>  #define V4L2_CTRL_CLASS_MPEG 0x00990000	/* MPEG-compression controls */
>  #define V4L2_CTRL_CLASS_CAMERA 0x009a0000	/* Camera class controls */
>  #define V4L2_CTRL_CLASS_FM_TX 0x009b0000	/* FM Modulator control class */
> +#define V4L2_CTRL_CLASS_FM_RX 0x009c0000	/* FM Tuner control class */
>  
>  #define V4L2_CTRL_ID_MASK      	  (0x0fffffff)
>  #define V4L2_CTRL_ID2CLASS(id)    ((id) & 0x0fff0000UL)
> @@ -1313,6 +1314,17 @@ enum v4l2_preemphasis {
>  #define V4L2_CID_TUNE_POWER_LEVEL		(V4L2_CID_FM_TX_CLASS_BASE + 113)
>  #define V4L2_CID_TUNE_ANTENNA_CAPACITOR		(V4L2_CID_FM_TX_CLASS_BASE + 114)
>  
> +/* FM Tuner class control IDs */
> +#define V4L2_CID_FM_RX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_RX | 0x900)
> +#define V4L2_CID_FM_RX_CLASS			(V4L2_CTRL_CLASS_FM_RX | 1)
> +
> +#define V4L2_CID_FM_RX_BAND			(V4L2_CID_FM_TX_CLASS_BASE + 1)
> +enum v4l2_fm_rx_band {

Just a very small change: rename v4l2_fm_rx_band to v4l2_fm_band. We might need
this enum later for transmitter devices as well so it is better to give it a
slightly more generic name.

> +	V4L2_FM_BAND_OTHER		= 0,
> +	V4L2_FM_BAND_JAPAN		= 1,
> +	V4L2_FM_BAND_OIRT		= 2
> +};
> +
>  /*
>   *	T U N I N G
>   */
> @@ -1377,7 +1389,8 @@ struct v4l2_hw_freq_seek {
>  	enum v4l2_tuner_type  type;
>  	__u32		      seek_upward;
>  	__u32		      wrap_around;
> -	__u32		      reserved[8];
> +	__u32		      spacing;
> +	__u32		      reserved[7];
>  };
>  
>  /*

Regards,

	Hans 

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec.
  2010-07-16 10:27     ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
  2010-07-16 10:27       ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
@ 2010-07-18  9:28       ` Hans Verkuil
  1 sibling, 0 replies; 11+ messages in thread
From: Hans Verkuil @ 2010-07-18  9:28 UTC (permalink / raw)
  To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin

On Friday 16 July 2010 12:27:45 Matti J. Aaltonen wrote:
> The codec handles digital audio input to and output from the
> WL1273 FM radio.

You might want to have this reviewed on the alsa mailinglist. I don't think
anyone on the linux-media list has the expertise to review this audio codec.

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio.
  2010-07-16 10:27       ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
  2010-07-16 10:27         ` [PATCH v5 5/5] Documentation: v4l: Add hw_seek spacing and FM_RX class Matti J. Aaltonen
@ 2010-07-18  9:52         ` Hans Verkuil
  1 sibling, 0 replies; 11+ messages in thread
From: Hans Verkuil @ 2010-07-18  9:52 UTC (permalink / raw)
  To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin

On Friday 16 July 2010 12:27:46 Matti J. Aaltonen wrote:
> This file implements V4L2 controls for using the Texas Instruments
> WL1273 FM Radio.
> 
> Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>

<snip>

> +
> +static unsigned int wl1273_fm_fops_poll(struct file *file,
> +					struct poll_table_struct *pts)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +	unsigned int rd_index, wr_index;
> +
> +	if (radio->owner && radio->owner != file)
> +		return -EBUSY;
> +
> +	radio->owner = file;
> +	poll_wait(file, &core->read_queue, pts);
> +
> +	rd_index = core->rd_index;
> +	wr_index = core->wr_index;
> +	if (rd_index != wr_index)
> +		return POLLIN | POLLOUT | POLLRDNORM;

You also need to add POLLWRNORM.

I wonder if this code is correct. Doesn't this depend on whether the device
is in receive or transmit mode? So either poll returns POLLIN|POLLRDNORM or
POLLOUT|POLLWRNORM. Or am I missing something?

> +
> +	return 0;
> +}

<snip>

> +#define WL1273_RDS_NOT_SYNCHRONIZED 0
> +#define WL1273_RDS_SYNCHRONIZED 1
> +
> +static int wl1273_fm_vidioc_g_tuner(struct file *file, void *priv,
> +				    struct v4l2_tuner *tuner)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +	u16 val;
> +	int r;
> +
> +	dev_dbg(radio->dev, "%s\n", __func__);
> +
> +	if (tuner->index > 0)
> +		return -EINVAL;
> +
> +	strcpy(tuner->name, WL1273_FM_DRIVER_NAME);

strlcpy

> +	tuner->type = V4L2_TUNER_RADIO;
> +
> +	tuner->rangelow	=
> +		WL1273_FREQ(core->bands[core->band].bottom_frequency);
> +	tuner->rangehigh =
> +		WL1273_FREQ(core->bands[core->band].top_frequency);
> +
> +	tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;

You can't detect whether mono or stereo is received? Does the alsa codec always
receive two channel audio? How does it handle mono vs stereo?

> +	tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_RDS;

Shouldn't CAP_STEREO be added?

> +
> +	if (core->mode != WL1273_MODE_RX)
> +		return 0;
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	r = wl1273_fm_read_reg(core, WL1273_RSSI_LVL_GET, &val);
> +	if (r)
> +		goto out;
> +
> +	tuner->signal = (s16) val;
> +	dev_dbg(radio->dev, "Signal: %d\n", tuner->signal);
> +
> +	tuner->afc = 0;
> +
> +	r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
> +	if (r)
> +		goto out;
> +
> +	if (val == WL1273_RDS_SYNCHRONIZED)
> +		tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;

tuner->audmode isn't filled!

> +out:
> +	mutex_unlock(&core->lock);
> +
> +	return r;
> +}
> +
> +static int wl1273_fm_vidioc_s_tuner(struct file *file, void *priv,
> +				    struct v4l2_tuner *tuner)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +	int r = 0;
> +
> +	dev_dbg(radio->dev, "%s\n", __func__);
> +	dev_dbg(radio->dev, "tuner->index: %d\n", tuner->index);
> +	dev_dbg(radio->dev, "tuner->name: %s\n", tuner->name);
> +	dev_dbg(radio->dev, "tuner->capability: 0x%04x\n", tuner->capability);
> +	dev_dbg(radio->dev, "tuner->rxsubchans: 0x%04x\n", tuner->rxsubchans);
> +	dev_dbg(radio->dev, "tuner->rangelow: %d\n", tuner->rangelow);
> +	dev_dbg(radio->dev, "tuner->rangehigh: %d\n", tuner->rangehigh);
> +
> +	if (tuner->index > 0)
> +		return -EINVAL;
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	r = wl1273_fm_set_mode(core, WL1273_MODE_RX);
> +	if (r)
> +		goto out;
> +
> +	if (tuner->rxsubchans & V4L2_TUNER_SUB_RDS)
> +		r = wl1273_fm_set_rds(core, WL1273_RDS_ON);
> +	else
> +		r = wl1273_fm_set_rds(core, WL1273_RDS_OFF);
> +
> +	if (r)
> +		dev_warn(radio->dev, "%s: RDS fails: %d\n", __func__, r);
> +
> +	if (tuner->audmode == V4L2_TUNER_MODE_MONO)
> +		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
> +					WL1273_RX_MONO);
> +	else
> +		r = wl1273_fm_write_cmd(core, WL1273_MOST_MODE_SET,
> +					WL1273_RX_STEREO);
> +	if (r < 0)
> +		dev_warn(radio->dev, WL1273_FM_DRIVER_NAME
> +			 ": set tuner mode failed with %d\n", r);
> +out:
> +	mutex_unlock(&core->lock);
> +
> +	return r;
> +}
> +
> +static int wl1273_fm_vidioc_g_frequency(struct file *file, void *priv,
> +					struct v4l2_frequency *freq)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +
> +	dev_dbg(radio->dev, "%s\n", __func__);
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	freq->type = V4L2_TUNER_RADIO;
> +	freq->frequency = WL1273_FREQ(wl1273_fm_get_freq(core));
> +
> +	mutex_unlock(&core->lock);
> +
> +	return 0;
> +}
> +
> +static int wl1273_fm_vidioc_s_frequency(struct file *file, void *priv,
> +					struct v4l2_frequency *freq)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +	int r;
> +
> +	dev_dbg(radio->dev, "%s: %d\n", __func__, freq->frequency);
> +
> +	if (freq->type != V4L2_TUNER_RADIO) {
> +		dev_dbg(radio->dev,
> +			"freq->type != V4L2_TUNER_RADIO: %d\n", freq->type);
> +		return -EINVAL;
> +	}
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	if (core->mode == WL1273_MODE_RX) {
> +		dev_dbg(radio->dev, "freq: %d\n", freq->frequency);
> +
> +		r = wl1273_fm_set_rx_freq(core,
> +					  WL1273_INV_FREQ(freq->frequency));
> +		if (r)
> +			dev_warn(radio->dev, WL1273_FM_DRIVER_NAME
> +				 ": set frequency failed with %d\n", r);
> +	} else {
> +		r = wl1273_fm_set_tx_freq(core,
> +					  WL1273_INV_FREQ(freq->frequency));
> +		if (r)
> +			dev_warn(radio->dev, WL1273_FM_DRIVER_NAME
> +				 ": set frequency failed with %d\n", r);
> +	}
> +
> +	mutex_unlock(&core->lock);
> +
> +	dev_dbg(radio->dev, "wl1273_vidioc_s_frequency: DONE\n");
> +	return r;
> +}
> +
> +#define WL1273_DEFAULT_SEEK_LEVEL	7
> +
> +static int wl1273_fm_vidioc_s_hw_freq_seek(struct file *file, void *priv,
> +					   struct v4l2_hw_freq_seek *seek)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +	int r;
> +
> +	dev_dbg(radio->dev, "%s\n", __func__);
> +
> +	if (seek->tuner != 0 || seek->type != V4L2_TUNER_RADIO)
> +		return -EINVAL;
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	r = wl1273_fm_set_mode(core, WL1273_MODE_RX);
> +	if (r)
> +		goto out;
> +
> +	r = wl1273_fm_tx_set_spacing(core, seek->spacing);
> +	if (r)
> +		dev_warn(radio->dev, "HW seek failed: %d\n", r);
> +
> +	r = wl1273_fm_set_seek(core, seek->wrap_around, seek->seek_upward,
> +			       WL1273_DEFAULT_SEEK_LEVEL);
> +	if (r)
> +		dev_warn(radio->dev, "HW seek failed: %d\n", r);
> +
> +	mutex_unlock(&core->lock);
> + out:
> +	return r;
> +}
> +
> +static int wl1273_fm_vidioc_s_modulator(struct file *file, void *priv,
> +					struct v4l2_modulator *modulator)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +	int r = 0;
> +
> +	dev_dbg(radio->dev, "%s\n", __func__);
> +
> +	if (modulator->index > 0)
> +		return -EINVAL;
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	r = wl1273_fm_set_mode(core, WL1273_MODE_TX);
> +	if (r)
> +		goto out;
> +
> +	if (modulator->txsubchans & V4L2_TUNER_SUB_RDS)
> +		r = wl1273_fm_set_rds(core, WL1273_RDS_ON);
> +	else
> +		r = wl1273_fm_set_rds(core, WL1273_RDS_OFF);

There is no support for SUB_MONO or SUB_STEREO?

> +
> +out:
> +	mutex_unlock(&core->lock);
> +
> +	return r;
> +}
> +
> +static int wl1273_fm_vidioc_g_modulator(struct file *file, void *priv,
> +					struct v4l2_modulator *modulator)
> +{
> +	struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
> +	struct wl1273_core *core = radio->core;
> +
> +	dev_dbg(radio->dev, "%s\n", __func__);
> +
> +	if (mutex_lock_interruptible(&core->lock))
> +		return -EINTR;
> +
> +	strcpy(modulator->name, WL1273_FM_DRIVER_NAME);
> +
> +	modulator->rangelow =
> +		WL1273_FREQ(core->bands[core->band].bottom_frequency);
> +	modulator->rangehigh =
> +		WL1273_FREQ(core->bands[core->band].top_frequency);
> +
> +	modulator->capability = V4L2_TUNER_CAP_RDS;

Shouldn't CAP_LOW and CAP_STEREO be added here?

> +
> +	if (core->rds_on)
> +		modulator->txsubchans |= V4L2_TUNER_SUB_RDS;
> +	else
> +		modulator->txsubchans &= ~V4L2_TUNER_SUB_RDS;

The SUB_MONO/SUB_STEREO flags aren't handled here.

> +
> +	mutex_unlock(&core->lock);
> +
> +	return 0;
> +}
> +

The g/s_tuner and g/s_modulator functions are always hard to get right. Lots of
tricky flags and settings...

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class.
  2010-07-16 10:27 ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
  2010-07-16 10:27   ` [PATCH v5 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
  2010-07-18  9:24   ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Hans Verkuil
@ 2010-07-18  9:55   ` Hans Verkuil
  2 siblings, 0 replies; 11+ messages in thread
From: Hans Verkuil @ 2010-07-18  9:55 UTC (permalink / raw)
  To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin

On Friday 16 July 2010 12:27:43 Matti J. Aaltonen wrote:
> Add spacing field to v4l2_hw_freq_seek and also add FM RX class to
> control classes.
> 
> Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
> ---
>  include/linux/videodev2.h |   15 ++++++++++++++-
>  1 files changed, 14 insertions(+), 1 deletions(-)
> 
> diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
> index 418dacf..95675cd 100644
> --- a/include/linux/videodev2.h
> +++ b/include/linux/videodev2.h
> @@ -935,6 +935,7 @@ struct v4l2_ext_controls {
>  #define V4L2_CTRL_CLASS_MPEG 0x00990000	/* MPEG-compression controls */
>  #define V4L2_CTRL_CLASS_CAMERA 0x009a0000	/* Camera class controls */
>  #define V4L2_CTRL_CLASS_FM_TX 0x009b0000	/* FM Modulator control class */
> +#define V4L2_CTRL_CLASS_FM_RX 0x009c0000	/* FM Tuner control class */
>  
>  #define V4L2_CTRL_ID_MASK      	  (0x0fffffff)
>  #define V4L2_CTRL_ID2CLASS(id)    ((id) & 0x0fff0000UL)
> @@ -1313,6 +1314,17 @@ enum v4l2_preemphasis {
>  #define V4L2_CID_TUNE_POWER_LEVEL		(V4L2_CID_FM_TX_CLASS_BASE + 113)
>  #define V4L2_CID_TUNE_ANTENNA_CAPACITOR		(V4L2_CID_FM_TX_CLASS_BASE + 114)
>  
> +/* FM Tuner class control IDs */
> +#define V4L2_CID_FM_RX_CLASS_BASE		(V4L2_CTRL_CLASS_FM_RX | 0x900)
> +#define V4L2_CID_FM_RX_CLASS			(V4L2_CTRL_CLASS_FM_RX | 1)
> +
> +#define V4L2_CID_FM_RX_BAND			(V4L2_CID_FM_TX_CLASS_BASE + 1)
> +enum v4l2_fm_rx_band {
> +	V4L2_FM_BAND_OTHER		= 0,
> +	V4L2_FM_BAND_JAPAN		= 1,
> +	V4L2_FM_BAND_OIRT		= 2
> +};

Note: you also need to add support for the new class and control to v4l2-common.c.
The following functions should be extended:

v4l2_ctrl_get_menu()
v4l2_ctrl_get_name()
v4l2_ctrl_query_fill()

Regards,

	Hans

> +
>  /*
>   *	T U N I N G
>   */
> @@ -1377,7 +1389,8 @@ struct v4l2_hw_freq_seek {
>  	enum v4l2_tuner_type  type;
>  	__u32		      seek_upward;
>  	__u32		      wrap_around;
> -	__u32		      reserved[8];
> +	__u32		      spacing;
> +	__u32		      reserved[7];
>  };
>  
>  /*
> 

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 5/5] Documentation: v4l: Add hw_seek spacing and FM_RX class
  2010-07-16 10:27         ` [PATCH v5 5/5] Documentation: v4l: Add hw_seek spacing and FM_RX class Matti J. Aaltonen
@ 2010-07-18  9:57           ` Hans Verkuil
  0 siblings, 0 replies; 11+ messages in thread
From: Hans Verkuil @ 2010-07-18  9:57 UTC (permalink / raw)
  To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin

On Friday 16 July 2010 12:27:47 Matti J. Aaltonen wrote:
> Add a couple of words about the spacing field in ithe HW seek struct and
> about the new FM RX control class.
> 
> Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
> ---
>  Documentation/DocBook/v4l/controls.xml             |   71 ++++++++++++++++++++
>  .../DocBook/v4l/vidioc-s-hw-freq-seek.xml          |   10 ++-
>  2 files changed, 79 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/DocBook/v4l/controls.xml b/Documentation/DocBook/v4l/controls.xml
> index e1bdbb6..9725f06 100644
> --- a/Documentation/DocBook/v4l/controls.xml
> +++ b/Documentation/DocBook/v4l/controls.xml
> @@ -2062,6 +2062,77 @@ manually or automatically if set to zero. Unit, range and step are driver-specif
>  <para>For more details about RDS specification, refer to
>  <xref linkend="en50067" /> document, from CENELEC.</para>
>      </section>
> +    <section id="fm-rx-controls">
> +      <title>FM Tuner Control Reference</title>
> +
> +      <para>The FM Tuner (FM_RX) class includes controls for common features of
> +devices that are capable of receiving FM transmissions. Currently this class includes a parameter
> +defining the FM radio band being used.</para>
> +
> +      <table pgwide="1" frame="none" id="fm-rx-control-id">
> +      <title>FM_RX Control IDs</title>
> +
> +      <tgroup cols="4">
> +	<colspec colname="c1" colwidth="1*" />
> +	<colspec colname="c2" colwidth="6*" />
> +	<colspec colname="c3" colwidth="2*" />
> +	<colspec colname="c4" colwidth="6*" />
> +	<spanspec namest="c1" nameend="c2" spanname="id" />
> +	<spanspec namest="c2" nameend="c4" spanname="descr" />
> +	<thead>
> +	  <row>
> +	    <entry spanname="id" align="left">ID</entry>
> +	    <entry align="left">Type</entry>
> +	  </row><row rowsep="1"><entry spanname="descr" align="left">Description</entry>
> +	  </row>
> +	</thead>
> +	<tbody valign="top">
> +	  <row><entry></entry></row>
> +	  <row>
> +	    <entry spanname="id"><constant>V4L2_CID_FM_RX_CLASS</constant>&nbsp;</entry>
> +	    <entry>class</entry>
> +	  </row><row><entry spanname="descr">The FM_RX class
> +descriptor. Calling &VIDIOC-QUERYCTRL; for this control will return a
> +description of this control class.</entry>
> +	  </row>
> +	  <row>
> +	    <entry spanname="id"><constant>V4L2_CID_FM_RX_BAND</constant>&nbsp;</entry>
> +	    <entry>integer</entry>
> +	  </row>
> +	  <row id="v4l2-fm_rx_band"><entry spanname="descr">Configures the FM radio band that is
> +the frequency range being used. Currentrly there are three band in use, see  <ulink
> +url="http://en.wikipedia.org/wiki/FM_broadcasting">Wikipedia</ulink>.
> +Usually 87.5 to 108.0 MHz is used, or some portion thereof, with a few exceptions:
> +In Japan, the band 76-90 MHz is used and
> +In the former Soviet republics, and some former Eastern Bloc countries,
> +the older 65-74 MHz band, referred also to as the OIRT band, is still used.
> +
> +The enum&nbsp; v4l2_fm_rx_band defines possible values for the FM band. They are:</entry>
> +	</row><row>
> +	<entrytbl spanname="descr" cols="2">
> +		  <tbody valign="top">
> +		    <row>
> +		      <entry><constant>V4L2_FM_BAND_OTHER</constant>&nbsp;</entry>
> +		      <entry>Frequencies from 87.5 to 108.0 MHz</entry>
> +		    </row>
> +		    <row>
> +		      <entry><constant>V4L2_FM_BAND_JAPAN</constant>&nbsp;</entry>
> +		      <entry>from 65 to 74 MHz</entry>
> +		    </row>
> +		    <row>
> +		      <entry><constant>V4L2_FM_BAND_OIRT</constant>&nbsp;</entry>
> +		      <entry>from 65 to 74 MHz</entry>
> +		    </row>
> +		  </tbody>
> +		</entrytbl>
> +
> +	  </row>
> +	  <row><entry></entry></row>
> +	</tbody>
> +      </tgroup>
> +      </table>
> +
> +    </section>
>  </section>
>  
>    <!--
> diff --git a/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml b/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml
> index 14b3ec7..8ee614c 100644
> --- a/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml
> +++ b/Documentation/DocBook/v4l/vidioc-s-hw-freq-seek.xml
> @@ -51,7 +51,8 @@
>  
>      <para>Start a hardware frequency seek from the current frequency.
>  To do this applications initialize the <structfield>tuner</structfield>,
> -<structfield>type</structfield>, <structfield>seek_upward</structfield> and
> +<structfield>type</structfield>, <structfield>seek_upward</structfield>,
> +<structfield>spacing</structfield> and
>  <structfield>wrap_around</structfield> fields, and zero out the
>  <structfield>reserved</structfield> array of a &v4l2-hw-freq-seek; and
>  call the <constant>VIDIOC_S_HW_FREQ_SEEK</constant> ioctl with a pointer
> @@ -89,7 +90,12 @@ field and the &v4l2-tuner; <structfield>index</structfield> field.</entry>
>  	  </row>
>  	  <row>
>  	    <entry>__u32</entry>
> -	    <entry><structfield>reserved</structfield>[8]</entry>
> +	    <entry><structfield>spacing</structfield></entry>
> +	    <entry>If non-zero, gives the search resolution to be used in hardware scan. The driver selects the nearest value that is supported by the hardware. If spacing is zero use a reasonable default value.</entry>

As Mauro's review mentioned: please specify the unit of this spacing field! (It should be Hz).

Don't forget to read and reply to Mauro's review as well!

Regards,

	Hans

> +	  </row>
> +	  <row>
> +	    <entry>__u32</entry>
> +	    <entry><structfield>reserved</structfield>[7]</entry>
>  	    <entry>Reserved for future extensions. Drivers and
>  	    applications must set the array to zero.</entry>
>  	  </row>
> 

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2010-07-18  9:54 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-07-16 10:27 [PATCH v5 0/5] WL1273 FM Radio driver Matti J. Aaltonen
2010-07-16 10:27 ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
2010-07-16 10:27   ` [PATCH v5 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
2010-07-16 10:27     ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
2010-07-16 10:27       ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
2010-07-16 10:27         ` [PATCH v5 5/5] Documentation: v4l: Add hw_seek spacing and FM_RX class Matti J. Aaltonen
2010-07-18  9:57           ` Hans Verkuil
2010-07-18  9:52         ` [PATCH v5 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Hans Verkuil
2010-07-18  9:28       ` [PATCH v5 3/5] ASoC: WL1273 FM Radio Digital audio codec Hans Verkuil
2010-07-18  9:24   ` [PATCH v5 1/5] V4L2: Add seek spacing and FM RX class Hans Verkuil
2010-07-18  9:55   ` Hans Verkuil

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.