* [PATCH v4 0/5] WL1273 FM Radio driver.
@ 2010-06-04 10:34 Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
0 siblings, 1 reply; 15+ messages in thread
From: Matti J. Aaltonen @ 2010-06-04 10:34 UTC (permalink / raw)
To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen
Hello again,
and thank you for the comments.
New in this version of the patch set:
General headers:
I removed the seek level stuff and added the FM RX class. And I've added the
BAND IOCTL and I defined the three existing bands: I also added the OIRT band
because I think it's nicer to have three bands than only two.
Hans wrote:
> Is there a difference in power consumption between the 'off' and 'suspend' state
> of the device? I assume that 'off' has the lowest power consumption.
> In that case I would have the driver go to suspend when no one has opened the
> radioX device. And if the driver is asked to go to suspend (that's the linux
> suspend state), then the driver can turn off the radio and on resume it will
> reload the firmware.
> Does that sound reasonable?
Yes that's reasonable... But I have had one problem here: the "suspend" does not
work, but we are working on it.
> As mentioned I don't think the level stuff should be added at the moment.
> The spacing field is no problem, but don't forget to update the V4L2 spec as
> well. Also document there what should happen if spacing == 0 (which is the
> case for existing apps). It basically boils down to the fact that the driver
> uses the spacing as a hint only and will adjust it to whatever the hardware
> supports.
I've fixed the spacing handling.
I've dropped the seek level stuff.
drivers/mfd/wl1273-core.c:
> Don't use bitfields! How bitfields are ordered is compiler specific.
I've dropped the bitfields.
> Does the data you copy here conform to the v4l2_rds_data struct?
> In particular the block byte. It is well documented in the Spec in the
> section on 'Reading RDS data'.
The error bits are not same as in the spec so I now copy them to the correct
positions and use the v4l2_rds_data struct.
drivers/media/radio/radio-wl1273.c:
> I understood that the band was relevant for receiving only?
That's true, I've removed the band stuff here.
> Rather than continually allocating and freeing this buffer I would just make
> it a field in struct wl1273_device. It's never more than 256 bytes, so that's
> no problem.
Now the buffer gets allocated and freed only once.
> Don't. Just make sure there can be only one reader. This is the same principle
> used by video: there can be multiple file handles open on a video node, but
> only one can be used for streaming at a time. Trying to handle multiple readers
> or writers in a driver will lead to chaos. And this can be done much better by
> userspace.
Now only one reader or writer is accepted.
> Where are the other FM_TX controls?
Added the missing controls. However, there's read-only control - I didn't add that.
> Use strlcpy here as well.
Now I'm using strlcpy everywhere...
> I strongly recommend using v4l2_ctrl_query_fill() instead (defined in
> v4l2-common.c). This ensures consistent naming. It will also make it easier
> to convert to the upcoming new control framework.
Replaced the old code with code that uses v4l2_ctrl_query_fill etc.
> Make this dev_dbg. I think it is probably better to pick the closest spacing
> rather than falling back to 50.
I've changed spacing handling quite a bit.
Cheers,
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.
.../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 | 1907 ++++++++++++++++++++
drivers/mfd/Kconfig | 6 +
drivers/mfd/Makefile | 2 +
drivers/mfd/wl1273-core.c | 616 +++++++
include/linux/mfd/wl1273-core.h | 326 ++++
include/linux/videodev2.h | 15 +-
sound/soc/codecs/Kconfig | 6 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wl1273.c | 594 ++++++
sound/soc/codecs/wl1273.h | 40 +
13 files changed, 3537 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] 15+ messages in thread
* [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class.
2010-06-04 10:34 [PATCH v4 0/5] WL1273 FM Radio driver Matti J. Aaltonen
@ 2010-06-04 10:34 ` Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
` (2 more replies)
0 siblings, 3 replies; 15+ messages in thread
From: Matti J. Aaltonen @ 2010-06-04 10:34 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] 15+ messages in thread
* [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
2010-06-04 10:34 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
@ 2010-06-04 10:34 ` Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
` (2 more replies)
2010-07-06 2:03 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Mauro Carvalho Chehab
2010-07-09 11:06 ` Hans Verkuil
2 siblings, 3 replies; 15+ messages in thread
From: Matti J. Aaltonen @ 2010-06-04 10:34 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/Kconfig | 6 +
drivers/mfd/Makefile | 2 +
drivers/mfd/wl1273-core.c | 616 +++++++++++++++++++++++++++++++++++++++
include/linux/mfd/wl1273-core.h | 326 +++++++++++++++++++++
4 files changed, 950 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/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
diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c
new file mode 100644
index 0000000..6c7dbba
--- /dev/null
+++ b/drivers/mfd/wl1273-core.c
@@ -0,0 +1,616 @@
+/*
+ * 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=Japan, 1=USA-Europe. USA-Europe is the default.
+ */
+static unsigned char radio_band = 1;
+module_param(radio_band, byte, 0);
+MODULE_PARM_DESC(radio_band, "Band: 0=Japan, 1=USA-Europe*");
+
+/*
+ * 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_RDS_FIFO_EMPTY(status) (!(1 << 6 & 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;
+ struct v4l2_rds_data rds = { 0, 0, 0 };
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = b0,
+ .len = 1
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = (u8 *) &rds,
+ .len = 3
+ }
+ };
+ u8 b0[] = { WL1273_RDS_DATA_GET }, status;
+ u16 val;
+ int r;
+
+ r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
+ if (r)
+ return r;
+
+ /* Is RDS decoder synchronized? */
+ if ((val & 0x01) == 0)
+ 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_RDS_FIFO_EMPTY(status))
+ break;
+
+ /* copy RDS block to internal buffer */
+ memcpy(&core->buffer[core->wr_index], &rds, 3);
+ core->wr_index += 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;
+ } else {
+ rds.block &= ~V4L2_RDS_BLOCK_ERROR;
+ rds.block &= ~V4L2_RDS_BLOCK_CORRECTED;
+ }
+
+ /* 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_RDS_FIFO_EMPTY(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..81c9743
--- /dev/null
+++ b/include/linux/mfd/wl1273-core.h
@@ -0,0 +1,326 @@
+/*
+ * 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 0
+#define WL1273_MODE_TX 1
+#define WL1273_MODE_OFF 2
+#define WL1273_MODE_SUSPENDED 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)
+
+/* Private IOCTL */
+#define WL1273_CID_FM_BAND (V4L2_CID_PRIVATE_BASE + 2)
+
+#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
+
+/* Allowed modes */
+#define WL1273_RX_ALLOWED 0x01
+#define WL1273_TX_ALLOWED 0x02
+#define WL1273_RXTX_ALLOWED (WL1273_RX_ALLOWED | WL1273_TX_ALLOWED)
+
+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 modes;
+ unsigned int children;
+};
+
+#define WL1273_FM_CORE_CELLS 2
+
+/* Allowed modes */
+#define WL1273_RX_ALLOWED 0x01
+#define WL1273_TX_ALLOWED 0x02
+#define WL1273_RXTX_ALLOWED (WL1273_RX_ALLOWED | WL1273_TX_ALLOWED)
+
+struct wl1273_core {
+ struct mfd_cell cells[WL1273_FM_CORE_CELLS];
+ struct i2c_client *i2c_dev;
+
+ u8 allowed_modes;
+ 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] 15+ messages in thread
* [PATCH v4 3/5] ASoC: WL1273 FM Radio Digital audio codec.
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
@ 2010-06-04 10:34 ` Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
2010-07-06 2:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver " Mauro Carvalho Chehab
2010-07-09 10:49 ` Hans Verkuil
2 siblings, 1 reply; 15+ messages in thread
From: Matti J. Aaltonen @ 2010-06-04 10:34 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 | 594 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/wl1273.h | 40 +++
4 files changed, 642 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..a2089ad
--- /dev/null
+++ b/sound/soc/codecs/wl1273.c
@@ -0,0 +1,594 @@
+/*
+ * 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;
+ }
+
+ r = snd_soc_init_card(socdev);
+ if (r < 0) {
+ dev_err(&pdev->dev, "Wl1273: failed to register card.\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] 15+ messages in thread
* [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio.
2010-06-04 10:34 ` [PATCH v4 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
@ 2010-06-04 10:34 ` Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 5/5] Documentation: v4l: Add hw_seek spacing Matti J. Aaltonen
2010-07-09 11:01 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Hans Verkuil
0 siblings, 2 replies; 15+ messages in thread
From: Matti J. Aaltonen @ 2010-06-04 10:34 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 | 1907 ++++++++++++++++++++++++++++++++++++
3 files changed, 1923 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..473c194
--- /dev/null
+++ b/drivers/media/radio/radio-wl1273.c
@@ -0,0 +1,1907 @@
+/*
+ * 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_MULT (10000 / 625)
+#define WL1273_INV_FREQ_MULT (625 / 10000)
+/*
+ * static unsigned char radio_band - Band
+ *
+ * The bands are 0=Japan, 1=USA-Europe. USA-Europe is the default.
+ */
+static unsigned char radio_band = 1;
+module_param(radio_band, byte, 0);
+MODULE_PARM_DESC(radio_band, "Band: 0=Japan, 1=USA-Europe*");
+
+/*
+ * 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)
+ goto err;
+
+ INIT_COMPLETION(core->busy);
+ r = wl1273_fm_write_cmd(core, WL1273_TUNER_MODE_SET,
+ TUNER_MODE_PRESET);
+ if (r) {
+ complete(&core->busy);
+ goto err;
+ }
+
+ r = wait_for_completion_timeout(&core->busy, msecs_to_jiffies(2000));
+ if (!r)
+ 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, "Allowed modes: %d\n",
+ core->allowed_modes);
+
+ mutex_lock(&core->lock);
+ old_mode = core->mode;
+
+ switch (mode) {
+ case WL1273_MODE_RX:
+ case WL1273_MODE_TX:
+ if (mode == WL1273_MODE_RX &&
+ !(core->allowed_modes & WL1273_RX_ALLOWED)) {
+ r = -EPERM;
+ goto out;
+ } else if (mode == WL1273_MODE_TX &&
+ !(core->allowed_modes & WL1273_TX_ALLOWED)) {
+ r = -EPERM;
+ goto out;
+ }
+
+ 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;
+ } else {
+ core->mode = mode;
+ r = wl1273_fm_set_audio(core, core->audio_mode);
+ if (r)
+ dev_err(&core->i2c_dev->dev,
+ "Cannot set audio mode.\n");
+ }
+
+ 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;
+ }
+ }
+
+ 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 ;
+
+ mutex_unlock(&core->lock);
+
+ 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__);
+
+ 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;
+
+ 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);
+
+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;
+ }
+
+ mutex_lock(&core->lock);
+
+ 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;
+
+ mutex_unlock(&core->lock);
+
+ 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));
+ unsigned char *s;
+ 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", s);
+
+ 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;
+
+ /* TODO: handle the case of multiple readers */
+
+ poll_wait(file, &core->read_queue, pts);
+
+ rd_index = core->rd_index;
+ wr_index = core->wr_index;
+ if (rd_index != wr_index)
+ return POLLIN | 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;
+
+ 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;
+
+ 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;
+
+ /* block if no new data available */
+ while (core->wr_index == core->rd_index) {
+ if (file->f_flags & O_NONBLOCK) {
+ r = -EWOULDBLOCK;
+ goto out;
+ }
+
+ 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 WL1273_CID_FM_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: {
+ u16 val;
+
+ r = wl1273_fm_read_reg(core, WL1273_VOLUME_SET, &val);
+ if (r)
+ break;
+
+ ctrl->value = val;
+ 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 WL1273_CID_FM_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 =
+ core->bands[core->band].bottom_frequency * WL1273_FREQ_MULT;
+ tuner->rangehigh =
+ core->bands[core->band].top_frequency * WL1273_FREQ_MULT;
+
+ 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 = val * 256;
+ 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__);
+
+ 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_fm_get_freq(core) * WL1273_FREQ_MULT;
+
+ 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;
+
+ wl1273_fm_write_cmd(core, WL1273_INT_MASK_SET, core->irq_flags);
+
+ if (core->mode == WL1273_MODE_RX) {
+ r = wl1273_fm_set_rx_freq(core, freq->frequency *
+ WL1273_INV_FREQ_MULT);
+ 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, freq->frequency *
+ WL1273_INV_FREQ_MULT);
+ 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;
+}
+
+static int wl1273_fm_vidioc_g_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;
+
+ dev_dbg(radio->dev, "%s\n", __func__);
+
+ if (core->mode != WL1273_MODE_RX)
+ return 0;
+
+ if (seek->tuner != 0 || seek->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ if (mutex_lock_interruptible(&core->lock))
+ return -EINTR;
+
+ mutex_unlock(&core->lock);
+
+ return 0;
+}
+
+#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;
+
+ modulator->rangelow =
+ core->bands[core->band].bottom_frequency * WL1273_FREQ_MULT;
+ modulator->rangehigh =
+ core->bands[core->band].top_frequency * WL1273_FREQ_MULT;
+
+ 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);
+
+ 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_g_hw_freq_seek = wl1273_fm_vidioc_g_hw_freq_seek,
+ .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");
--
1.6.1.3
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 5/5] Documentation: v4l: Add hw_seek spacing.
2010-06-04 10:34 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
@ 2010-06-04 10:34 ` Matti J. Aaltonen
2010-07-06 2:44 ` Mauro Carvalho Chehab
2010-07-09 11:01 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Hans Verkuil
1 sibling, 1 reply; 15+ messages in thread
From: Matti J. Aaltonen @ 2010-06-04 10:34 UTC (permalink / raw)
To: linux-media, hverkuil, eduardo.valentin; +Cc: Matti J. Aaltonen
Add a couple of words about the spacing field in HW seek struct.
Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
---
.../DocBook/v4l/vidioc-s-hw-freq-seek.xml | 10 ++++++++--
1 files changed, 8 insertions(+), 2 deletions(-)
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] 15+ messages in thread
* Re: [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class.
2010-06-04 10:34 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
@ 2010-07-06 2:03 ` Mauro Carvalho Chehab
2010-07-19 7:55 ` m7aalton
2010-07-09 11:06 ` Hans Verkuil
2 siblings, 1 reply; 15+ messages in thread
From: Mauro Carvalho Chehab @ 2010-07-06 2:03 UTC (permalink / raw)
To: Matti J. Aaltonen; +Cc: linux-media, hverkuil, eduardo.valentin
Em 04-06-2010 07:34, Matti J. Aaltonen escreveu:
> Add spacing field to v4l2_hw_freq_seek and also add FM RX class to
> control classes.
If you're adding new stuff to API, you should also patch Documentation/DocBook/v4l stuff.
>
> 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
> +};
You don't need anything special to define the bandwidth. VIDIOC_G_TUNER/VIDIOC_S_TUNER allows
negotiating rangelow/rangehigh.
> +
> /*
> * 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];
> };
>
> /*
I can't comment on your other API proposals, as you didn't send a patch documenting it
at the API spec.
Cheers,
Mauro.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
@ 2010-07-06 2:34 ` Mauro Carvalho Chehab
2010-07-07 5:21 ` Pavan Savoy
2010-07-19 7:43 ` m7aalton
2010-07-09 10:49 ` Hans Verkuil
2 siblings, 2 replies; 15+ messages in thread
From: Mauro Carvalho Chehab @ 2010-07-06 2:34 UTC (permalink / raw)
To: Matti J. Aaltonen; +Cc: linux-media, hverkuil, eduardo.valentin
Em 04-06-2010 07:34, Matti J. Aaltonen escreveu:
> 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/Kconfig | 6 +
> drivers/mfd/Makefile | 2 +
> drivers/mfd/wl1273-core.c | 616 +++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/wl1273-core.h | 326 +++++++++++++++++++++
> 4 files changed, 950 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/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
> diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c
> new file mode 100644
> index 0000000..6c7dbba
> --- /dev/null
> +++ b/drivers/mfd/wl1273-core.c
> @@ -0,0 +1,616 @@
> +/*
> + * 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=Japan, 1=USA-Europe. USA-Europe is the default.
> + */
> +static unsigned char radio_band = 1;
> +module_param(radio_band, byte, 0);
> +MODULE_PARM_DESC(radio_band, "Band: 0=Japan, 1=USA-Europe*");
> +
> +/*
> + * 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_RDS_FIFO_EMPTY(status) (!(1 << 6 & 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;
> + struct v4l2_rds_data rds = { 0, 0, 0 };
> + struct i2c_msg msg[] = {
> + {
> + .addr = client->addr,
> + .flags = 0,
> + .buf = b0,
> + .len = 1
> + },
> + {
> + .addr = client->addr,
> + .flags = I2C_M_RD,
> + .buf = (u8 *) &rds,
> + .len = 3
> + }
> + };
> + u8 b0[] = { WL1273_RDS_DATA_GET }, status;
> + u16 val;
> + int r;
> +
> + r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
> + if (r)
> + return r;
> +
> + /* Is RDS decoder synchronized? */
> + if ((val & 0x01) == 0)
> + 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_RDS_FIFO_EMPTY(status))
> + break;
> +
> + /* copy RDS block to internal buffer */
> + memcpy(&core->buffer[core->wr_index], &rds, 3);
> + core->wr_index += 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;
> + } else {
> + rds.block &= ~V4L2_RDS_BLOCK_ERROR;
> + rds.block &= ~V4L2_RDS_BLOCK_CORRECTED;
> + }
> +
> + /* 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_RDS_FIFO_EMPTY(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);
msleep on an irq handler? You shouldn't be doing it! You're not allowed to sleep
during IRQ time. Kernel can panic here. You'll probably need to defer work and
handle it outside irq time.
> +
> + 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..81c9743
> --- /dev/null
> +++ b/include/linux/mfd/wl1273-core.h
> @@ -0,0 +1,326 @@
> +/*
> + * 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 0
> +#define WL1273_MODE_TX 1
> +#define WL1273_MODE_OFF 2
> +#define WL1273_MODE_SUSPENDED 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)
> +
> +/* Private IOCTL */
> +#define WL1273_CID_FM_BAND (V4L2_CID_PRIVATE_BASE + 2)
> +
> +#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
> +
> +/* Allowed modes */
> +#define WL1273_RX_ALLOWED 0x01
> +#define WL1273_TX_ALLOWED 0x02
> +#define WL1273_RXTX_ALLOWED (WL1273_RX_ALLOWED | WL1273_TX_ALLOWED)
> +
> +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 modes;
> + unsigned int children;
> +};
> +
> +#define WL1273_FM_CORE_CELLS 2
> +
> +/* Allowed modes */
> +#define WL1273_RX_ALLOWED 0x01
> +#define WL1273_TX_ALLOWED 0x02
> +#define WL1273_RXTX_ALLOWED (WL1273_RX_ALLOWED | WL1273_TX_ALLOWED)
> +
> +struct wl1273_core {
> + struct mfd_cell cells[WL1273_FM_CORE_CELLS];
> + struct i2c_client *i2c_dev;
> +
> + u8 allowed_modes;
> + 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 */
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 5/5] Documentation: v4l: Add hw_seek spacing.
2010-06-04 10:34 ` [PATCH v4 5/5] Documentation: v4l: Add hw_seek spacing Matti J. Aaltonen
@ 2010-07-06 2:44 ` Mauro Carvalho Chehab
0 siblings, 0 replies; 15+ messages in thread
From: Mauro Carvalho Chehab @ 2010-07-06 2:44 UTC (permalink / raw)
To: Matti J. Aaltonen; +Cc: linux-media, hverkuil, eduardo.valentin
Em 04-06-2010 07:34, Matti J. Aaltonen escreveu:
> Add a couple of words about the spacing field in HW seek struct.
>
> Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
> ---
> .../DocBook/v4l/vidioc-s-hw-freq-seek.xml | 10 ++++++++--
> 1 files changed, 8 insertions(+), 2 deletions(-)
>
> 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>
What's the unit for spacing?
> + </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>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
2010-07-06 2:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver " Mauro Carvalho Chehab
@ 2010-07-07 5:21 ` Pavan Savoy
2010-07-19 7:43 ` m7aalton
1 sibling, 0 replies; 15+ messages in thread
From: Pavan Savoy @ 2010-07-07 5:21 UTC (permalink / raw)
To: Matti J. Aaltonen, Mauro Carvalho Chehab
Cc: linux-media, hverkuil, eduardo.valentin, pavan savoy
--- On Tue, 6/7/10, Mauro Carvalho Chehab <mchehab@redhat.com> wrote:
> From: Mauro Carvalho Chehab <mchehab@redhat.com>
> Subject: Re: [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
> To: "Matti J. Aaltonen" <matti.j.aaltonen@nokia.com>
> Cc: linux-media@vger.kernel.org, hverkuil@xs4all.nl, eduardo.valentin@nokia.com
> Date: Tuesday, 6 July, 2010, 8:04 AM
> Em 04-06-2010 07:34, Matti J.
> Aaltonen escreveu:
> > 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.
Where can I have a look at the whole code ? As in some local tree, where all codes are put up ?
Basically, we have a V4L2 driver for WL128x (using TTY as transport) and plan to push them soon ...
This would be a nice input ...
> > Signed-off-by: Matti J. Aaltonen <matti.j.aaltonen@nokia.com>
> > ---
> > drivers/mfd/Kconfig
> | 6 +
> > drivers/mfd/Makefile
> | 2 +
> > drivers/mfd/wl1273-core.c
> | 616
> +++++++++++++++++++++++++++++++++++++++
> > include/linux/mfd/wl1273-core.h | 326
> +++++++++++++++++++++
> > 4 files changed, 950 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/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
> > diff --git a/drivers/mfd/wl1273-core.c
> b/drivers/mfd/wl1273-core.c
> > new file mode 100644
> > index 0000000..6c7dbba
> > --- /dev/null
> > +++ b/drivers/mfd/wl1273-core.c
> > @@ -0,0 +1,616 @@
> > +/*
> > + * 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=Japan, 1=USA-Europe. USA-Europe is
> the default.
> > + */
> > +static unsigned char radio_band = 1;
> > +module_param(radio_band, byte, 0);
> > +MODULE_PARM_DESC(radio_band, "Band: 0=Japan,
> 1=USA-Europe*");
> > +
> > +/*
> > + * 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_RDS_FIFO_EMPTY(status) (!(1
> << 6 & 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;
> > + struct v4l2_rds_data rds = { 0, 0,
> 0 };
> > + struct i2c_msg msg[] = {
> > + {
> > +
> .addr = client->addr,
> > +
> .flags = 0,
> > +
> .buf = b0,
> > +
> .len = 1
> > + },
> > + {
> > +
> .addr = client->addr,
> > +
> .flags = I2C_M_RD,
> > +
> .buf = (u8 *) &rds,
> > +
> .len = 3
> > + }
> > + };
> > + u8 b0[] = { WL1273_RDS_DATA_GET },
> status;
> > + u16 val;
> > + int r;
> > +
> > + r = wl1273_fm_read_reg(core,
> WL1273_RDS_SYNC_GET, &val);
> > + if (r)
> > + return r;
> > +
> > + /* Is RDS decoder synchronized?
> */
> > + if ((val & 0x01) == 0)
> > + 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_RDS_FIFO_EMPTY(status))
> > +
> break;
> > +
> > + /* copy RDS
> block to internal buffer */
> > +
> memcpy(&core->buffer[core->wr_index], &rds,
> 3);
> > +
> core->wr_index += 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;
> > + } else {
> > +
> rds.block &= ~V4L2_RDS_BLOCK_ERROR;
> > +
> rds.block &=
> ~V4L2_RDS_BLOCK_CORRECTED;
> > + }
> > +
> > + /* 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_RDS_FIFO_EMPTY(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);
>
>
> msleep on an irq handler? You shouldn't be doing it! You're
> not allowed to sleep
> during IRQ time. Kernel can panic here. You'll probably
> need to defer work and
> handle it outside irq time.
> > +
> > +
> 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..81c9743
> > --- /dev/null
> > +++ b/include/linux/mfd/wl1273-core.h
> > @@ -0,0 +1,326 @@
> > +/*
> > + * 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
> 0
> > +#define WL1273_MODE_TX
> 1
> > +#define WL1273_MODE_OFF
> 2
> > +#define WL1273_MODE_SUSPENDED 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)
> > +
> > +/* Private IOCTL */
> > +#define WL1273_CID_FM_BAND
> (V4L2_CID_PRIVATE_BASE + 2)
> > +
> > +#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
> > +
> > +/* Allowed modes */
> > +#define WL1273_RX_ALLOWED 0x01
> > +#define WL1273_TX_ALLOWED 0x02
> > +#define WL1273_RXTX_ALLOWED
> (WL1273_RX_ALLOWED | WL1273_TX_ALLOWED)
> > +
> > +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 modes;
> > + unsigned int children;
> > +};
> > +
> > +#define WL1273_FM_CORE_CELLS 2
> > +
> > +/* Allowed modes */
> > +#define WL1273_RX_ALLOWED 0x01
> > +#define WL1273_TX_ALLOWED 0x02
> > +#define WL1273_RXTX_ALLOWED
> (WL1273_RX_ALLOWED | WL1273_TX_ALLOWED)
> > +
> > +struct wl1273_core {
> > + struct mfd_cell
> cells[WL1273_FM_CORE_CELLS];
> > + struct i2c_client *i2c_dev;
> > +
> > + u8 allowed_modes;
> > + 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 */
>
> --
> To unsubscribe from this list: send the line "unsubscribe
> linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
2010-07-06 2:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver " Mauro Carvalho Chehab
@ 2010-07-09 10:49 ` Hans Verkuil
2 siblings, 0 replies; 15+ messages in thread
From: Hans Verkuil @ 2010-07-09 10:49 UTC (permalink / raw)
To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin
Hi Matti,
My apologies for the long delay in reviewing. But here is (finally!) my
review of this code.
On Friday 04 June 2010 12:34:20 Matti J. Aaltonen wrote:
> 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/Kconfig | 6 +
> drivers/mfd/Makefile | 2 +
> drivers/mfd/wl1273-core.c | 616 +++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/wl1273-core.h | 326 +++++++++++++++++++++
> 4 files changed, 950 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/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
> diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c
> new file mode 100644
> index 0000000..6c7dbba
> --- /dev/null
> +++ b/drivers/mfd/wl1273-core.c
> @@ -0,0 +1,616 @@
> +/*
> + * 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=Japan, 1=USA-Europe. USA-Europe is the default.
> + */
> +static unsigned char radio_band = 1;
> +module_param(radio_band, byte, 0);
> +MODULE_PARM_DESC(radio_band, "Band: 0=Japan, 1=USA-Europe*");
> +
> +/*
> + * 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_RDS_FIFO_EMPTY(status) (!(1 << 6 & 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;
> + struct v4l2_rds_data rds = { 0, 0, 0 };
> + struct i2c_msg msg[] = {
> + {
> + .addr = client->addr,
> + .flags = 0,
> + .buf = b0,
> + .len = 1
> + },
> + {
> + .addr = client->addr,
> + .flags = I2C_M_RD,
> + .buf = (u8 *) &rds,
> + .len = 3
> + }
> + };
> + u8 b0[] = { WL1273_RDS_DATA_GET }, status;
> + u16 val;
> + int r;
> +
> + r = wl1273_fm_read_reg(core, WL1273_RDS_SYNC_GET, &val);
> + if (r)
> + return r;
> +
> + /* Is RDS decoder synchronized? */
> + if ((val & 0x01) == 0)
> + 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_RDS_FIFO_EMPTY(status))
> + break;
> +
> + /* copy RDS block to internal buffer */
> + memcpy(&core->buffer[core->wr_index], &rds, 3);
> + core->wr_index += 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;
> + } else {
> + rds.block &= ~V4L2_RDS_BLOCK_ERROR;
> + rds.block &= ~V4L2_RDS_BLOCK_CORRECTED;
> + }
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.
<snip>
Regards,
Hans
--
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio.
2010-06-04 10:34 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 5/5] Documentation: v4l: Add hw_seek spacing Matti J. Aaltonen
@ 2010-07-09 11:01 ` Hans Verkuil
1 sibling, 0 replies; 15+ messages in thread
From: Hans Verkuil @ 2010-07-09 11:01 UTC (permalink / raw)
To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin
On Friday 04 June 2010 12:34:22 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>
> ---
> drivers/media/radio/Kconfig | 15 +
> drivers/media/radio/Makefile | 1 +
> drivers/media/radio/radio-wl1273.c | 1907 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 1923 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..473c194
> --- /dev/null
> +++ b/drivers/media/radio/radio-wl1273.c
> @@ -0,0 +1,1907 @@
> +/*
> + * 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_MULT (10000 / 625)
> +#define WL1273_INV_FREQ_MULT (625 / 10000)
> +/*
> + * static unsigned char radio_band - Band
> + *
> + * The bands are 0=Japan, 1=USA-Europe. USA-Europe is the default.
> + */
> +static unsigned char radio_band = 1;
> +module_param(radio_band, byte, 0);
> +MODULE_PARM_DESC(radio_band, "Band: 0=Japan, 1=USA-Europe*");
> +
> +/*
> + * 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)
> + goto err;
> +
> + INIT_COMPLETION(core->busy);
> + r = wl1273_fm_write_cmd(core, WL1273_TUNER_MODE_SET,
> + TUNER_MODE_PRESET);
> + if (r) {
> + complete(&core->busy);
> + goto err;
> + }
> +
> + r = wait_for_completion_timeout(&core->busy, msecs_to_jiffies(2000));
> + if (!r)
> + 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, "Allowed modes: %d\n",
> + core->allowed_modes);
> +
> + mutex_lock(&core->lock);
> + old_mode = core->mode;
> +
> + switch (mode) {
> + case WL1273_MODE_RX:
> + case WL1273_MODE_TX:
> + if (mode == WL1273_MODE_RX &&
> + !(core->allowed_modes & WL1273_RX_ALLOWED)) {
> + r = -EPERM;
> + goto out;
> + } else if (mode == WL1273_MODE_TX &&
> + !(core->allowed_modes & WL1273_TX_ALLOWED)) {
> + r = -EPERM;
> + goto out;
> + }
> +
> + 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;
> + } else {
> + core->mode = mode;
> + r = wl1273_fm_set_audio(core, core->audio_mode);
> + if (r)
> + dev_err(&core->i2c_dev->dev,
> + "Cannot set audio mode.\n");
> + }
> +
> + 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;
> + }
> + }
> +
> + 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 ;
> +
> + mutex_unlock(&core->lock);
> +
> + 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__);
> +
> + 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;
> +
> + 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);
> +
> +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;
> + }
> +
> + mutex_lock(&core->lock);
> +
> + 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;
> +
> + mutex_unlock(&core->lock);
> +
> + 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));
> + unsigned char *s;
> + 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", s);
> +
> + 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;
> +
> + /* 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.
> +
> + poll_wait(file, &core->read_queue, pts);
> +
> + rd_index = core->rd_index;
> + wr_index = core->wr_index;
> + if (rd_index != wr_index)
> + return POLLIN | POLLRDNORM;
Since you can write as well, shouldn't there be POLLOUT handling too?
> +
> + return 0;
> +}
<snip>
Regards,
Hans
--
Hans Verkuil - video4linux developer - sponsored by TANDBERG, part of Cisco
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class.
2010-06-04 10:34 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
2010-07-06 2:03 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Mauro Carvalho Chehab
@ 2010-07-09 11:06 ` Hans Verkuil
2 siblings, 0 replies; 15+ messages in thread
From: Hans Verkuil @ 2010-07-09 11:06 UTC (permalink / raw)
To: Matti J. Aaltonen; +Cc: linux-media, eduardo.valentin
On Friday 04 June 2010 12:34:19 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
> +};
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.
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] 15+ messages in thread
* Re: [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio.
2010-07-06 2:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver " Mauro Carvalho Chehab
2010-07-07 5:21 ` Pavan Savoy
@ 2010-07-19 7:43 ` m7aalton
1 sibling, 0 replies; 15+ messages in thread
From: m7aalton @ 2010-07-19 7:43 UTC (permalink / raw)
To: ext Mauro Carvalho Chehab; +Cc: linux-media, hverkuil, Valentin Eduardo
Hello and
thanks for the comments Mauro... One quick comment:
On Tue, 2010-07-06 at 04:34 +0200, ext Mauro Carvalho Chehab wrote:
> > + 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);
>
>
> msleep on an irq handler? You shouldn't be doing it! You're not allowed to sleep
> during IRQ time. Kernel can panic here. You'll probably need to defer work and
> handle it outside irq time.
This is not the IRQ handler, this is the irq thread function.
> > +
> > + r = request_threaded_irq(client->irq, NULL,
> > + wl1273_fm_irq_thread_handler,
> > + IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
> > + "wl1273-fm", core);
B.R.
Matti A.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class.
2010-07-06 2:03 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Mauro Carvalho Chehab
@ 2010-07-19 7:55 ` m7aalton
0 siblings, 0 replies; 15+ messages in thread
From: m7aalton @ 2010-07-19 7:55 UTC (permalink / raw)
To: ext Mauro Carvalho Chehab; +Cc: linux-media, hverkuil, Valentin Eduardo
Hello.
On Tue, 2010-07-06 at 04:03 +0200, ext Mauro Carvalho Chehab wrote:
> Em 04-06-2010 07:34, Matti J. Aaltonen escreveu:
> > +
> > +#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
> > +};
>
>
> You don't need anything special to define the bandwidth. VIDIOC_G_TUNER/VIDIOC_S_TUNER allows
> negotiating rangelow/rangehigh.
I don't quite get how you want this to be done... Are you saying that
this enum should not be there? At least the wl1273 chip actually
supports two bands so it would seem more straightforward to start by
defining the band and working from there than the other way round.
B.R.
Matti A.
>
> > +
> > /*
> > * 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];
> > };
> >
> > /*
>
> I can't comment on your other API proposals, as you didn't send a patch documenting it
> at the API spec.
>
> Cheers,
> Mauro.
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2010-07-19 7:56 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-06-04 10:34 [PATCH v4 0/5] WL1273 FM Radio driver Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver for the FM radio Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 3/5] ASoC: WL1273 FM Radio Digital audio codec Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Matti J. Aaltonen
2010-06-04 10:34 ` [PATCH v4 5/5] Documentation: v4l: Add hw_seek spacing Matti J. Aaltonen
2010-07-06 2:44 ` Mauro Carvalho Chehab
2010-07-09 11:01 ` [PATCH v4 4/5] V4L2: WL1273 FM Radio: Controls for the FM radio Hans Verkuil
2010-07-06 2:34 ` [PATCH v4 2/5] MFD: WL1273 FM Radio: MFD driver " Mauro Carvalho Chehab
2010-07-07 5:21 ` Pavan Savoy
2010-07-19 7:43 ` m7aalton
2010-07-09 10:49 ` Hans Verkuil
2010-07-06 2:03 ` [PATCH v4 1/5] V4L2: Add seek spacing and FM RX class Mauro Carvalho Chehab
2010-07-19 7:55 ` m7aalton
2010-07-09 11:06 ` Hans Verkuil
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).