diff mbox

[1/2] V4L: Add BCM2048 radio driver

Message ID 12420240811376-git-send-email-ext-eero.nurkkala@nokia.com (mailing list archive)
State Changes Requested
Delegated to: Mauro Carvalho Chehab
Headers show

Commit Message

ext-eero.nurkkala@nokia.com May 11, 2009, 6:41 a.m. UTC
From: Eero Nurkkala <ext-eero.nurkkala@nokia.com>

This adds V4L support for the BCM2048 radio module.

Signed-off-by: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
---
 drivers/media/radio/radio-bcm2048.c | 2613 +++++++++++++++++++++++++++++++++++
 include/media/radio-bcm2048.h       |   30 +
 2 files changed, 2643 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/radio/radio-bcm2048.c
 create mode 100644 include/media/radio-bcm2048.h
diff mbox

Patch

diff --git a/drivers/media/radio/radio-bcm2048.c b/drivers/media/radio/radio-bcm2048.c
new file mode 100644
index 0000000..43dbdee
--- /dev/null
+++ b/drivers/media/radio/radio-bcm2048.c
@@ -0,0 +1,2613 @@ 
+/*
+ * drivers/media/radio/radio-bcm2048.c
+ *
+ * Driver for I2C Broadcom BCM2048 FM Radio Receiver:
+ *
+ * Copyright (C) Nokia Corporation
+ * Contact: Eero Nurkkala <ext-eero.nurkkala@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
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/sysfs.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/radio-bcm2048.h>
+
+/* driver definitions */
+#define BCM2048_DRIVER_AUTHOR	"Eero Nurkkala <ext-eero.nurkkala@nokia.com>"
+#define BCM2048_DRIVER_NAME	BCM2048_NAME
+#define BCM2048_DRIVER_VERSION	KERNEL_VERSION(0, 0, 1)
+#define BCM2048_DRIVER_CARD	"Broadcom bcm2048 FM Radio Receiver"
+#define BCM2048_DRIVER_DESC	"I2C driver for BCM2048 FM Radio Receiver"
+
+/* I2C Control Registers */
+#define BCM2048_I2C_FM_RDS_SYSTEM	0x00
+#define BCM2048_I2C_FM_CTRL		0x01
+#define BCM2048_I2C_RDS_CTRL0		0x02
+#define BCM2048_I2C_RDS_CTRL1		0x03
+#define BCM2048_I2C_FM_AUDIO_PAUSE	0x04
+#define BCM2048_I2C_FM_AUDIO_CTRL0	0x05
+#define BCM2048_I2C_FM_AUDIO_CTRL1	0x06
+#define BCM2048_I2C_FM_SEARCH_CTRL0	0x07
+#define BCM2048_I2C_FM_SEARCH_CTRL1	0x08
+#define BCM2048_I2C_FM_SEARCH_TUNE_MODE	0x09
+#define BCM2048_I2C_FM_FREQ0		0x0a
+#define BCM2048_I2C_FM_FREQ1		0x0b
+#define BCM2048_I2C_FM_AF_FREQ0		0x0c
+#define BCM2048_I2C_FM_AF_FREQ1		0x0d
+#define BCM2048_I2C_FM_CARRIER		0x0e
+#define BCM2048_I2C_FM_RSSI		0x0f
+#define BCM2048_I2C_FM_RDS_MASK0	0x10
+#define BCM2048_I2C_FM_RDS_MASK1	0x11
+#define BCM2048_I2C_FM_RDS_FLAG0	0x12
+#define BCM2048_I2C_FM_RDS_FLAG1	0x13
+#define BCM2048_I2C_RDS_WLINE		0x14
+#define BCM2048_I2C_RDS_BLKB_MATCH0	0x16
+#define BCM2048_I2C_RDS_BLKB_MATCH1	0x17
+#define BCM2048_I2C_RDS_BLKB_MASK0	0x18
+#define BCM2048_I2C_RDS_BLKB_MASK1	0x19
+#define BCM2048_I2C_RDS_PI_MATCH0	0x1a
+#define BCM2048_I2C_RDS_PI_MATCH1	0x1b
+#define BCM2048_I2C_RDS_PI_MASK0	0x1c
+#define BCM2048_I2C_RDS_PI_MASK1	0x1d
+#define BCM2048_I2C_SPARE1		0x20
+#define BCM2048_I2C_SPARE2		0x21
+#define BCM2048_I2C_FM_RDS_REV		0x28
+#define BCM2048_I2C_SLAVE_CONFIGURATION	0x29
+#define BCM2048_I2C_RDS_DATA		0x80
+#define BCM2048_I2C_FM_BEST_TUNE_MODE	0x90
+
+/* BCM2048_I2C_FM_RDS_SYSTEM */
+#define BCM2048_FM_ON			0x01
+#define BCM2048_RDS_ON			0x02
+
+/* BCM2048_I2C_FM_CTRL */
+#define BCM2048_BAND_SELECT			0x01
+#define BCM2048_STEREO_MONO_AUTO_SELECT		0x02
+#define BCM2048_STEREO_MONO_MANUAL_SELECT	0x04
+#define BCM2048_STEREO_MONO_BLEND_SWITCH	0x08
+#define BCM2048_HI_LO_INJECTION			0x10
+
+/* BCM2048_I2C_RDS_CTRL0 */
+#define BCM2048_RBDS_RDS_SELECT		0x01
+#define BCM2048_FLUSH_FIFO		0x02
+
+/* BCM2048_I2C_FM_AUDIO_PAUSE */
+#define BCM2048_AUDIO_PAUSE_RSSI_TRESH	0x0f
+#define BCM2048_AUDIO_PAUSE_DURATION	0xf0
+
+/* BCM2048_I2C_FM_AUDIO_CTRL0 */
+#define BCM2048_RF_MUTE			0x01
+#define BCM2048_MANUAL_MUTE		0x02
+#define BCM2048_DAC_OUTPUT_LEFT		0x04
+#define BCM2048_DAC_OUTPUT_RIGHT	0x08
+#define BCM2048_AUDIO_ROUTE_DAC		0x10
+#define BCM2048_AUDIO_ROUTE_I2S		0x20
+#define BCM2048_DE_EMPHASIS_SELECT	0x40
+#define BCM2048_AUDIO_BANDWIDTH_SELECT	0x80
+
+/* BCM2048_I2C_FM_SEARCH_CTRL0 */
+#define BCM2048_SEARCH_RSSI_THRESHOLD	0x7f
+#define BCM2048_SEARCH_DIRECTION	0x80
+
+/* BCM2048_I2C_FM_SEARCH_TUNE_MODE */
+#define BCM2048_FM_AUTO_SEARCH		0x03
+
+/* BCM2048_I2C_FM_RSSI */
+#define BCM2048_RSSI_VALUE		0xff
+
+/* BCM2048_I2C_FM_RDS_MASK0 */
+/* BCM2048_I2C_FM_RDS_MASK1 */
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED	0x01
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL	0x02
+#define BCM2048_FM_FLAG_RSSI_LOW		0x04
+#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH	0x08
+#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION	0x10
+#define BCM2048_FLAG_STEREO_DETECTED		0x20
+#define BCM2048_FLAG_STEREO_ACTIVE		0x40
+
+/* BCM2048_I2C_RDS_DATA */
+#define BCM2048_SLAVE_ADDRESS			0x3f
+#define BCM2048_SLAVE_ENABLE			0x80
+
+/* BCM2048_I2C_FM_BEST_TUNE_MODE */
+#define BCM2048_BEST_TUNE_MODE			0x80
+
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED	0x01
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL	0x02
+#define BCM2048_FM_FLAG_RSSI_LOW		0x04
+#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH	0x08
+#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION	0x10
+#define BCM2048_FLAG_STEREO_DETECTED		0x20
+#define BCM2048_FLAG_STEREO_ACTIVE		0x40
+
+#define BCM2048_RDS_FLAG_FIFO_WLINE		0x02
+#define BCM2048_RDS_FLAG_B_BLOCK_MATCH		0x08
+#define BCM2048_RDS_FLAG_SYNC_LOST		0x10
+#define BCM2048_RDS_FLAG_PI_MATCH		0x20
+
+#define BCM2048_RDS_MARK_END_BYTE0		0x7C
+#define BCM2048_RDS_MARK_END_BYTEN		0xFF
+
+#define BCM2048_FM_FLAGS_ALL	(FM_FLAG_SEARCH_TUNE_FINISHED | \
+				 FM_FLAG_SEARCH_TUNE_FAIL | \
+				 FM_FLAG_RSSI_LOW | \
+				 FM_FLAG_CARRIER_ERROR_HIGH | \
+				 FM_FLAG_AUDIO_PAUSE_INDICATION | \
+				 FLAG_STEREO_DETECTED | FLAG_STEREO_ACTIVE)
+
+#define BCM2048_RDS_FLAGS_ALL	(RDS_FLAG_FIFO_WLINE | \
+				 RDS_FLAG_B_BLOCK_MATCH | \
+				 RDS_FLAG_SYNC_LOST | RDS_FLAG_PI_MATCH)
+
+#define BCM2048_DEFAULT_TIMEOUT		1500
+#define BCM2048_AUTO_SEARCH_TIMEOUT	3000
+
+
+#define BCM2048_FREQDEV_UNIT		10000
+#define BCM2048_FREQV4L2_MULTI		625
+#define dev_to_v4l2(f)	((f * BCM2048_FREQDEV_UNIT) / BCM2048_FREQV4L2_MULTI)
+#define v4l2_to_dev(f)	((f * BCM2048_FREQV4L2_MULTI) / BCM2048_FREQDEV_UNIT)
+
+#define msb(x)                  ((u8)((u16) x >> 8))
+#define lsb(x)                  ((u8)((u16) x &  0x00FF))
+#define compose_u16(msb, lsb)	(((u16)msb << 8) | lsb)
+
+#define BCM2048_DEFAULT_POWERING_DELAY	20
+#define BCM2048_DEFAULT_REGION		0x02
+#define BCM2048_DEFAULT_MUTE		0x01
+#define BCM2048_DEFAULT_RSSI_THRESHOLD	0x64
+#define BCM2048_DEFAULT_RDS_WLINE	0x7E
+
+#define BCM2048_FM_SEARCH_INACTIVE	0x00
+#define BCM2048_FM_PRE_SET_MODE		0x01
+#define BCM2048_FM_AUTO_SEARCH_MODE	0x02
+#define BCM2048_FM_AF_JUMP_MODE		0x03
+
+#define BCM2048_FREQUENCY_BASE		64000
+
+#define BCM2048_POWER_ON		0x01
+#define BCM2048_POWER_OFF		0x00
+
+#define BCM2048_ITEM_ENABLED		0x01
+#define BCM2048_SEARCH_DIRECTION_UP	0x01
+
+#define BCM2048_DE_EMPHASIS_75us	75
+#define BCM2048_DE_EMPHASIS_50us	50
+
+#define BCM2048_SCAN_FAIL		0x00
+#define BCM2048_SCAN_OK			0x01
+
+#define BCM2048_FREQ_ERROR_FLOOR	-20
+#define BCM2048_FREQ_ERROR_ROOF		20
+
+/* -60 dB is reported as full signal strenght */
+#define BCM2048_RSSI_LEVEL_BASE		-60
+#define BCM2048_RSSI_LEVEL_ROOF		-100
+#define BCM2048_RSSI_LEVEL_ROOF_NEG	100
+#define BCM2048_SIGNAL_MULTIPLIER	(0xFFFF / \
+					 (BCM2048_RSSI_LEVEL_ROOF_NEG + \
+					  BCM2048_RSSI_LEVEL_BASE))
+
+#define BCM2048_RDS_FIFO_DUPLE_SIZE	0x03
+#define BCM2048_RDS_CRC_MASK		0x0F
+#define BCM2048_RDS_CRC_NONE		0x00
+#define BCM2048_RDS_CRC_MAX_2BITS	0x04
+#define BCM2048_RDS_CRC_LEAST_2BITS	0x08
+#define BCM2048_RDS_CRC_UNRECOVARABLE	0x0C
+
+#define BCM2048_RDS_BLOCK_MASK		0xF0
+#define BCM2048_RDS_BLOCK_A		0x00
+#define BCM2048_RDS_BLOCK_B		0x10
+#define BCM2048_RDS_BLOCK_C		0x20
+#define BCM2048_RDS_BLOCK_D		0x30
+#define BCM2048_RDS_BLOCK_C_SCORED	0x40
+#define BCM2048_RDS_BLOCK_E		0x60
+
+#define BCM2048_RDS_RT			0x20
+#define BCM2048_RDS_PS			0x00
+
+#define BCM2048_RDS_GROUP_AB_MASK	0x08
+#define BCM2048_RDS_GROUP_A		0x00
+#define BCM2048_RDS_GROUP_B		0x08
+
+#define BCM2048_RDS_RT_AB_MASK		0x10
+#define BCM2048_RDS_RT_A		0x00
+#define BCM2048_RDS_RT_B		0x10
+#define BCM2048_RDS_RT_INDEX		0x0F
+
+#define BCM2048_RDS_PS_INDEX		0x03
+
+struct rds_info {
+	u16 rds_pi;
+#define BCM2048_MAX_RDS_RT (64 + 1)
+	u8 rds_rt[BCM2048_MAX_RDS_RT];
+	u8 rds_rt_group_b;
+	u8 rds_rt_ab;
+#define BCM2048_MAX_RDS_PS (8 + 1)
+	u8 rds_ps[BCM2048_MAX_RDS_PS];
+	u8 rds_ps_group;
+	u8 rds_ps_group_cnt;
+#define BCM2048_MAX_RDS_RADIO_TEXT 255
+	u8 radio_text[BCM2048_MAX_RDS_RADIO_TEXT + 3];
+	u8 text_len;
+};
+
+struct region_info {
+	u32 bottom_frequency;
+	u32 top_frequency;
+	u8 deemphasis;
+	u8 channel_spacing;
+	u8 region;
+};
+
+struct bcm2048_device {
+	struct i2c_client *client;
+	struct video_device *videodev;
+	struct work_struct work;
+	struct completion compl;
+	struct mutex mutex;
+	struct bcm2048_platform_data *platform_data;
+	struct rds_info rds_info;
+	struct region_info region_info;
+	u16 frequency;
+	u8 cache_fm_rds_system;
+	u8 cache_fm_ctrl;
+	u8 cache_fm_audio_ctrl0;
+	u8 cache_fm_search_ctrl0;
+	u8 power_state;
+	u8 rds_state;
+	u8 fifo_size;
+	u8 scan_state;
+	u8 mute_state;
+};
+
+static int radio_nr = -1;	/* radio device minor (-1 ==> auto assign) */
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(radio_nr,
+		 "Minor number for radio device (-1 ==> auto assign)");
+
+static struct region_info region_configs[] = {
+	/* USA */
+	{
+		.channel_spacing	= 20,
+		.bottom_frequency	= 87500,
+		.top_frequency		= 108000,
+		.deemphasis		= 75,
+		.region			= 0,
+	},
+	/* Australia */
+	{
+		.channel_spacing	= 20,
+		.bottom_frequency	= 87500,
+		.top_frequency		= 108000,
+		.deemphasis		= 50,
+		.region			= 1,
+	},
+	/* Europe */
+	{
+		.channel_spacing	= 10,
+		.bottom_frequency	= 87500,
+		.top_frequency		= 108000,
+		.deemphasis		= 50,
+		.region			= 2,
+	},
+	/* Japan */
+	{
+		.channel_spacing	= 10,
+		.bottom_frequency	= 76000,
+		.top_frequency		= 90000,
+		.deemphasis		= 50,
+		.region			= 3,
+	},
+	/* Japan wide band */
+	{
+		.channel_spacing	= 10,
+		.bottom_frequency	= 76000,
+		.top_frequency		= 108000,
+		.deemphasis		= 50,
+		.region			= 4,
+	},
+};
+
+/*
+ *	I2C Interface read / write
+ */
+static int bcm2048_send_command(struct bcm2048_device *bdev, unsigned int reg,
+					unsigned int value)
+{
+	struct i2c_client *client = bdev->client;
+	u8 data[2];
+
+	if (!bdev->power_state) {
+		dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
+		return -EIO;
+	}
+
+	data[0] = reg & 0xff;
+	data[1] = value & 0xff;
+
+	if (i2c_master_send(client, data, 2) == 2) {
+		return 0;
+	} else {
+		dev_err(&bdev->client->dev, "BCM I2C error!\n");
+		dev_err(&bdev->client->dev, "Is Bluetooth up and running?\n");
+		return -EIO;
+	}
+}
+
+static int bcm2048_recv_command(struct bcm2048_device *bdev, unsigned int reg,
+			u8 *value)
+{
+	struct i2c_client *client = bdev->client;
+
+	if (!bdev->power_state) {
+		dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
+		return -EIO;
+	}
+
+	value[0] = i2c_smbus_read_byte_data(client, reg & 0xff);
+
+	return 0;
+}
+
+static int bcm2048_recv_duples(struct bcm2048_device *bdev, unsigned int reg,
+			u8 *value, u8 duples)
+{
+	struct i2c_client *client = bdev->client;
+	struct i2c_adapter *adap = client->adapter;
+	struct i2c_msg msg[2];
+	u8 buf;
+
+	if (!bdev->power_state) {
+		dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
+		return -EIO;
+	}
+
+	buf = reg & 0xff;
+
+	msg[0].addr = client->addr;
+	msg[0].flags = client->flags & I2C_M_TEN;
+	msg[0].len = 1;
+	msg[0].buf = &buf;
+
+	msg[1].addr = client->addr;
+	msg[1].flags = client->flags & I2C_M_TEN;
+	msg[1].flags |= I2C_M_RD;
+	msg[1].len = duples;
+	msg[1].buf = value;
+
+	return i2c_transfer(adap, msg, 2);
+}
+
+/*
+ *	BCM2048 - I2C register programming helpers
+ */
+static int bcm2048_set_power_state(struct bcm2048_device *bdev, u8 power)
+{
+	int err = 0;
+
+	mutex_lock(&bdev->mutex);
+
+	if (power) {
+		bdev->power_state = BCM2048_POWER_ON;
+		bdev->cache_fm_rds_system |= BCM2048_FM_ON;
+	} else {
+		bdev->cache_fm_rds_system &= ~BCM2048_FM_ON;
+	}
+
+	/*
+	 * Warning! FM cannot be turned off because then
+	 * the I2C communications get ruined!
+	 * Comment off the "if (power)" when the chip works!
+	 */
+	if (power)
+		err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM,
+					bdev->cache_fm_rds_system);
+	msleep(BCM2048_DEFAULT_POWERING_DELAY);
+
+	if (!power)
+		bdev->power_state = BCM2048_POWER_OFF;
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_power_state(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err && (value & BCM2048_FM_ON))
+		return BCM2048_POWER_ON;
+
+	return err;
+}
+
+static int bcm2048_set_rds_no_lock(struct bcm2048_device *bdev, u8 rds_on)
+{
+	int err;
+	u8 flags;
+
+	bdev->cache_fm_rds_system &= ~BCM2048_RDS_ON;
+
+	if (rds_on) {
+		bdev->cache_fm_rds_system |= BCM2048_RDS_ON;
+		bdev->rds_state = BCM2048_RDS_ON;
+		flags =	BCM2048_RDS_FLAG_FIFO_WLINE;
+		err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+						flags);
+	} else {
+		flags =	0;
+		bdev->rds_state = 0;
+		err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+						flags);
+		memset(&bdev->rds_info, 0, sizeof(bdev->rds_info));
+	}
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM,
+					bdev->cache_fm_rds_system);
+
+	return err;
+}
+
+static int bcm2048_get_rds_no_lock(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value);
+
+	if (!err && (value & BCM2048_RDS_ON))
+		return BCM2048_ITEM_ENABLED;
+
+	return err;
+}
+
+static int bcm2048_set_rds(struct bcm2048_device *bdev, u8 rds_on)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_set_rds_no_lock(bdev, rds_on);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds(struct bcm2048_device *bdev)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_get_rds_no_lock(bdev);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_pi(struct bcm2048_device *bdev)
+{
+	return bdev->rds_info.rds_pi;
+}
+
+static int bcm2048_set_fm_automatic_stereo_mono(struct bcm2048_device *bdev,
+						u8 enabled)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	bdev->cache_fm_ctrl &= ~BCM2048_STEREO_MONO_AUTO_SELECT;
+
+	if (enabled)
+		bdev->cache_fm_ctrl |= BCM2048_STEREO_MONO_AUTO_SELECT;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
+					bdev->cache_fm_ctrl);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_set_fm_hi_lo_injection(struct bcm2048_device *bdev,
+						u8 hi_lo)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	bdev->cache_fm_ctrl &= ~BCM2048_HI_LO_INJECTION;
+
+	if (hi_lo)
+		bdev->cache_fm_ctrl |= BCM2048_HI_LO_INJECTION;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
+					bdev->cache_fm_ctrl);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_hi_lo_injection(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CTRL, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err && (value & BCM2048_HI_LO_INJECTION))
+		return BCM2048_ITEM_ENABLED;
+
+	return err;
+}
+
+static int bcm2048_set_fm_frequency(struct bcm2048_device *bdev, u32 frequency)
+{
+	int err;
+
+	if (frequency < bdev->region_info.bottom_frequency ||
+		frequency > bdev->region_info.top_frequency)
+		return -EDOM;
+
+	frequency -= BCM2048_FREQUENCY_BASE;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ0, lsb(frequency));
+	err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ1,
+					msb(frequency));
+
+	if (!err)
+		bdev->frequency = frequency;
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_frequency(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 lsb, msb;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ0, &lsb);
+	err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ1, &msb);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (err)
+		return err;
+
+	err = compose_u16(msb, lsb);
+	err += BCM2048_FREQUENCY_BASE;
+
+	return err;
+}
+
+static int bcm2048_set_fm_af_frequency(struct bcm2048_device *bdev,
+						u32 frequency)
+{
+	int err;
+
+	if (frequency < bdev->region_info.bottom_frequency ||
+		frequency > bdev->region_info.top_frequency)
+		return -EDOM;
+
+	frequency -= BCM2048_FREQUENCY_BASE;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ0,
+					lsb(frequency));
+	err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ1,
+					msb(frequency));
+	if (!err)
+		bdev->frequency = frequency;
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_af_frequency(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 lsb, msb;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ0, &lsb);
+	err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ1, &msb);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (err)
+		return err;
+
+	err = compose_u16(msb, lsb);
+	err += BCM2048_FREQUENCY_BASE;
+
+	return err;
+}
+
+static int bcm2048_set_fm_deemphasis(struct bcm2048_device *bdev, int d)
+{
+	int err;
+	u8 deemphasis;
+
+	if (d == BCM2048_DE_EMPHASIS_75us)
+		deemphasis = BCM2048_DE_EMPHASIS_SELECT;
+	else
+		deemphasis = 0;
+
+	mutex_lock(&bdev->mutex);
+
+	bdev->cache_fm_audio_ctrl0 &= ~BCM2048_DE_EMPHASIS_SELECT;
+	bdev->cache_fm_audio_ctrl0 |= deemphasis;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+		bdev->cache_fm_audio_ctrl0);
+
+	if (!err)
+		bdev->region_info.deemphasis = d;
+
+	mutex_unlock(&bdev->mutex);
+
+	return err;
+}
+
+static int bcm2048_get_fm_deemphasis(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err) {
+		if (value & BCM2048_DE_EMPHASIS_SELECT)
+			return BCM2048_DE_EMPHASIS_75us;
+		else
+			return BCM2048_DE_EMPHASIS_50us;
+	}
+
+	return err;
+}
+
+static int bcm2048_set_region(struct bcm2048_device *bdev, u8 region)
+{
+	int err;
+	u32 new_frequency = 0;
+
+	if (region > ARRAY_SIZE(region_configs))
+		return -EINVAL;
+
+	mutex_lock(&bdev->mutex);
+	memcpy(&bdev->region_info, &region_configs[region],
+		sizeof(struct region_info));
+	mutex_unlock(&bdev->mutex);
+
+	if (bdev->frequency < region_configs[region].bottom_frequency ||
+		bdev->frequency > region_configs[region].top_frequency)
+		new_frequency = region_configs[region].bottom_frequency;
+
+	if (new_frequency > 0) {
+		err = bcm2048_set_fm_frequency(bdev, new_frequency);
+
+		if (err)
+			goto done;
+	}
+
+	err = bcm2048_set_fm_deemphasis(bdev,
+			region_configs[region].deemphasis);
+
+done:
+	return err;
+}
+
+static int bcm2048_get_region(struct bcm2048_device *bdev)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+	err = bdev->region_info.region;
+	mutex_unlock(&bdev->mutex);
+
+	return err;
+}
+
+static int bcm2048_set_mute(struct bcm2048_device *bdev, u16 mute)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE);
+
+	if (mute)
+		bdev->cache_fm_audio_ctrl0 |= (BCM2048_RF_MUTE |
+						BCM2048_MANUAL_MUTE);
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+					bdev->cache_fm_audio_ctrl0);
+
+	if (!err)
+		bdev->mute_state = mute;
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_mute(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	if (bdev->power_state) {
+		err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+						&value);
+		if (!err)
+			err = value & (BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE);
+	} else {
+		err = bdev->mute_state;
+	}
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_set_audio_route(struct bcm2048_device *bdev, u8 route)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	route &= (BCM2048_AUDIO_ROUTE_DAC | BCM2048_AUDIO_ROUTE_I2S);
+	bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_AUDIO_ROUTE_DAC |
+		BCM2048_AUDIO_ROUTE_I2S);
+	bdev->cache_fm_audio_ctrl0 |= route;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+					bdev->cache_fm_audio_ctrl0);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_audio_route(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return value & (BCM2048_AUDIO_ROUTE_DAC |
+			BCM2048_AUDIO_ROUTE_I2S);
+
+	return err;
+}
+
+static int bcm2048_set_dac_output(struct bcm2048_device *bdev, u8 channels)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_DAC_OUTPUT_LEFT |
+					BCM2048_DAC_OUTPUT_RIGHT);
+	bdev->cache_fm_audio_ctrl0 |= channels;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+					bdev->cache_fm_audio_ctrl0);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_dac_output(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return value & (BCM2048_DAC_OUTPUT_LEFT |
+			BCM2048_DAC_OUTPUT_RIGHT);
+
+	return err;
+}
+
+static int bcm2048_set_fm_search_rssi_threshold(struct bcm2048_device *bdev,
+							u8 threshold)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	threshold &= BCM2048_SEARCH_RSSI_THRESHOLD;
+	bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_RSSI_THRESHOLD;
+	bdev->cache_fm_search_ctrl0 |= threshold;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0,
+					bdev->cache_fm_search_ctrl0);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_search_rssi_threshold(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return value & BCM2048_SEARCH_RSSI_THRESHOLD;
+
+	return err;
+}
+
+static int bcm2048_set_fm_search_mode_direction(struct bcm2048_device *bdev,
+						u8 direction)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_DIRECTION;
+
+	if (direction)
+		bdev->cache_fm_search_ctrl0 |= BCM2048_SEARCH_DIRECTION;
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0,
+					bdev->cache_fm_search_ctrl0);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_search_mode_direction(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err && (value & BCM2048_SEARCH_DIRECTION))
+		return BCM2048_SEARCH_DIRECTION_UP;
+
+	return err;
+}
+
+static int bcm2048_set_fm_search_tune_mode(struct bcm2048_device *bdev,
+						u8 mode)
+{
+	int err, timeout, restart_rds = 0;
+	u8 value, flags;
+
+	value = mode & BCM2048_FM_AUTO_SEARCH;
+
+	flags =	BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED |
+		BCM2048_FM_FLAG_SEARCH_TUNE_FAIL;
+
+	mutex_lock(&bdev->mutex);
+
+	/*
+	 * If RDS is enabled, and frequency is changed, RDS quits working.
+	 * Thus, always restart RDS if it's enabled. Moreover, RDS must
+	 * not be enabled while changing the frequency because it can
+	 * provide a race to the mutex from the workqueue handler if RDS
+	 * IRQ occurs while waiting for frequency changed IRQ.
+	 */
+	if (bcm2048_get_rds_no_lock(bdev)) {
+		err = bcm2048_set_rds_no_lock(bdev, 0);
+		if (err)
+			goto unlock;
+		restart_rds = 1;
+	}
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK0, flags);
+
+	if (err)
+		goto unlock;
+
+	bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, value);
+
+	if (mode != BCM2048_FM_AUTO_SEARCH_MODE)
+		timeout = BCM2048_DEFAULT_TIMEOUT;
+	else
+		timeout = BCM2048_AUTO_SEARCH_TIMEOUT;
+
+	if (!wait_for_completion_timeout(&bdev->compl,
+			msecs_to_jiffies(timeout)))
+			dev_err(&bdev->client->dev, "IRQ timeout.\n");
+
+	if (value)
+		if (!bdev->scan_state)
+			err = -EIO;
+
+unlock:
+	if (restart_rds)
+		err |= bcm2048_set_rds_no_lock(bdev, 1);
+
+	mutex_unlock(&bdev->mutex);
+
+	return err;
+}
+
+static int bcm2048_get_fm_search_tune_mode(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE,
+					&value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return value & BCM2048_FM_AUTO_SEARCH;
+
+	return err;
+}
+
+static int bcm2048_set_rds_b_block_mask(struct bcm2048_device *bdev, u16 mask)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MASK0, lsb(mask));
+	err |= bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MASK1, msb(mask));
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_b_block_mask(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 lsb, msb;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MASK0, &lsb);
+	err |= bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MASK1, &msb);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return compose_u16(msb, lsb);
+
+	return err;
+}
+
+static int bcm2048_set_rds_b_block_match(struct bcm2048_device *bdev,
+						u16 match)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MATCH0, lsb(match));
+	err |= bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MATCH1, msb(match));
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_b_block_match(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 lsb, msb;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MATCH0, &lsb);
+	err |= bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_BLKB_MATCH1, &msb);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return compose_u16(msb, lsb);
+
+	return err;
+}
+
+static int bcm2048_set_rds_pi_mask(struct bcm2048_device *bdev, u16 mask)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_PI_MASK0, lsb(mask));
+	err |= bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_PI_MASK1, msb(mask));
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_pi_mask(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 lsb, msb;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_PI_MASK0, &lsb);
+	err |= bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_PI_MASK1, &msb);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return compose_u16(msb, lsb);
+
+	return err;
+}
+
+static int bcm2048_set_rds_pi_match(struct bcm2048_device *bdev, u16 match)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_PI_MATCH0, lsb(match));
+	err |= bcm2048_send_command(bdev,
+			BCM2048_I2C_RDS_PI_MATCH1, msb(match));
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_pi_match(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 lsb, msb;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_PI_MATCH0, &lsb);
+	err |= bcm2048_recv_command(bdev,
+			BCM2048_I2C_RDS_PI_MATCH1, &msb);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return compose_u16(msb, lsb);
+
+	return err;
+}
+
+static int bcm2048_set_fm_rds_mask(struct bcm2048_device *bdev, u16 mask)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev,
+			BCM2048_I2C_FM_RDS_MASK0, lsb(mask));
+	err |= bcm2048_send_command(bdev,
+			BCM2048_I2C_FM_RDS_MASK1, msb(mask));
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_rds_mask(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value0, value1;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK0, &value0);
+	err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK1, &value1);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return compose_u16(value1, value0);
+
+	return err;
+}
+
+static int bcm2048_get_fm_rds_flags(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value0, value1;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &value0);
+	err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &value1);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return compose_u16(value1, value0);
+
+	return err;
+}
+
+static int bcm2048_get_region_bottom_frequency(struct bcm2048_device *bdev)
+{
+	return bdev->region_info.bottom_frequency;
+}
+
+static int bcm2048_get_region_top_frequency(struct bcm2048_device *bdev)
+{
+	return bdev->region_info.top_frequency;
+}
+
+static int bcm2048_set_fm_best_tune_mode(struct bcm2048_device *bdev, u8 mode)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	/* Perform read as the manual indicates */
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
+					&value);
+	value &= ~BCM2048_BEST_TUNE_MODE;
+
+	if (mode)
+		value |= BCM2048_BEST_TUNE_MODE;
+	err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
+					value);
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_fm_best_tune_mode(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
+					&value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err && (value & BCM2048_BEST_TUNE_MODE))
+		return BCM2048_ITEM_ENABLED;
+
+	return err;
+}
+
+static int bcm2048_get_fm_carrier_error(struct bcm2048_device *bdev)
+{
+	int err = 0;
+	s8 value;
+
+	mutex_lock(&bdev->mutex);
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CARRIER, &value);
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return value;
+
+	return err;
+}
+
+static int bcm2048_get_fm_rssi(struct bcm2048_device *bdev)
+{
+	int err;
+	s8 value;
+
+	mutex_lock(&bdev->mutex);
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RSSI, &value);
+	mutex_unlock(&bdev->mutex);
+
+	if (!err)
+		return value;
+
+	return err;
+}
+
+static int bcm2048_set_rds_wline(struct bcm2048_device *bdev, u8 wline)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_WLINE, wline);
+
+	if (!err)
+		bdev->fifo_size = wline;
+
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_wline(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 value;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_WLINE, &value);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err) {
+		bdev->fifo_size = value;
+		return value;
+	}
+
+	return err;
+}
+
+static int bcm2048_checkrev(struct bcm2048_device *bdev)
+{
+	int err;
+	u8 version;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_REV, &version);
+
+	mutex_unlock(&bdev->mutex);
+
+	if (!err) {
+		dev_info(&bdev->client->dev, "BCM2048 Version 0x%x\n",
+			version);
+		return version;
+	}
+
+	return err;
+}
+
+static int bcm2048_get_rds_rt(struct bcm2048_device *bdev, char *data)
+{
+	int err = 0, i, j = 0, ce = 0, cr = 0;
+	char data_buffer[BCM2048_MAX_RDS_RT+1];
+
+	mutex_lock(&bdev->mutex);
+
+	if (!bdev->rds_info.text_len) {
+		err = -EINVAL;
+		goto unlock;
+	}
+
+	for (i = 0; i < BCM2048_MAX_RDS_RT; i++) {
+		if (bdev->rds_info.rds_rt[i]) {
+			ce = i;
+			/* Skip the carriage return */
+			if (bdev->rds_info.rds_rt[i] != 0x0d) {
+				data_buffer[j++] = bdev->rds_info.rds_rt[i];
+			} else {
+				cr = i;
+				break;
+			}
+		}
+	}
+
+	if (j <= BCM2048_MAX_RDS_RT)
+		data_buffer[j] = 0;
+
+	for (i = 0; i < BCM2048_MAX_RDS_RT; i++) {
+		if (!bdev->rds_info.rds_rt[i]) {
+			if (cr && (i < cr)) {
+				err = -EBUSY;
+				goto unlock;
+			}
+			if (i < ce) {
+				if (cr && (i >= cr))
+					break;
+				err = -EBUSY;
+				goto unlock;
+			}
+		}
+	}
+
+	memcpy(data, data_buffer, sizeof(data_buffer));
+
+unlock:
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static int bcm2048_get_rds_ps(struct bcm2048_device *bdev, char *data)
+{
+	int err = 0, i, j = 0;
+	char data_buffer[BCM2048_MAX_RDS_PS+1];
+
+	mutex_lock(&bdev->mutex);
+
+	if (!bdev->rds_info.text_len) {
+		err = -EINVAL;
+		goto unlock;
+	}
+
+	for (i = 0; i < BCM2048_MAX_RDS_PS; i++) {
+		if (bdev->rds_info.rds_ps[i]) {
+			data_buffer[j++] = bdev->rds_info.rds_ps[i];
+		} else {
+			if (i < (BCM2048_MAX_RDS_PS - 1)) {
+				err = -EBUSY;
+				goto unlock;
+			}
+		}
+	}
+
+	if (j <= BCM2048_MAX_RDS_PS)
+		data_buffer[j] = 0;
+
+	memcpy(data, data_buffer, sizeof(data_buffer));
+
+unlock:
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+static void bcm2048_parse_rds_pi(struct bcm2048_device *bdev)
+{
+	int i, cnt = 0;
+	u16 pi;
+
+	for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
+
+		/* Block A match, only data without crc errors taken */
+		if (bdev->rds_info.radio_text[i] == BCM2048_RDS_BLOCK_A) {
+
+			pi = ((bdev->rds_info.radio_text[i+1] << 8) +
+				bdev->rds_info.radio_text[i+2]);
+
+			if (!bdev->rds_info.rds_pi) {
+				bdev->rds_info.rds_pi = pi;
+				return;
+			}
+			if (pi != bdev->rds_info.rds_pi) {
+				cnt++;
+				if (cnt > 3) {
+					bdev->rds_info.rds_pi = pi;
+					cnt = 0;
+				}
+			} else {
+				cnt = 0;
+			}
+		}
+	}
+}
+
+static int bcm2048_rds_block_crc(struct bcm2048_device *bdev, int i)
+{
+	return bdev->rds_info.radio_text[i] & BCM2048_RDS_CRC_MASK;
+}
+
+static void bcm2048_parse_rds_rt_block(struct bcm2048_device *bdev, int i,
+					int index, int crc)
+{
+	/* Good data will overwrite poor data */
+	if (crc) {
+		if (!bdev->rds_info.rds_rt[index])
+			bdev->rds_info.rds_rt[index] =
+				bdev->rds_info.radio_text[i+1];
+		if (!bdev->rds_info.rds_rt[index+1])
+			bdev->rds_info.rds_rt[index+1] =
+				bdev->rds_info.radio_text[i+2];
+	} else {
+		bdev->rds_info.rds_rt[index] = bdev->rds_info.radio_text[i+1];
+		bdev->rds_info.rds_rt[index+1] =
+			bdev->rds_info.radio_text[i+2];
+	}
+}
+
+static int bcm2048_parse_rt_match_b(struct bcm2048_device *bdev, int i)
+{
+	int crc, rt_id, rt_group_b, rt_ab, index = 0;
+
+	crc = bcm2048_rds_block_crc(bdev, i);
+
+	if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+		return -EIO;
+
+	if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+		BCM2048_RDS_BLOCK_B) {
+
+		rt_id = (bdev->rds_info.radio_text[i+1] &
+			BCM2048_RDS_BLOCK_MASK);
+		rt_group_b = bdev->rds_info.radio_text[i+1] &
+			BCM2048_RDS_GROUP_AB_MASK;
+		rt_ab = bdev->rds_info.radio_text[i+2] &
+				BCM2048_RDS_RT_AB_MASK;
+
+		if (rt_group_b != bdev->rds_info.rds_rt_group_b) {
+			memset(bdev->rds_info.rds_rt, 0,
+				sizeof(bdev->rds_info.rds_rt));
+			bdev->rds_info.rds_rt_group_b = rt_group_b;
+		}
+
+		if (rt_id == BCM2048_RDS_RT) {
+			/* A to B or (vice versa), means: clear screen */
+			if (rt_ab != bdev->rds_info.rds_rt_ab) {
+				memset(bdev->rds_info.rds_rt, 0,
+					sizeof(bdev->rds_info.rds_rt));
+				bdev->rds_info.rds_rt_ab = rt_ab;
+			}
+
+			index = bdev->rds_info.radio_text[i+2] &
+					BCM2048_RDS_RT_INDEX;
+
+			if (bdev->rds_info.rds_rt_group_b)
+				index <<= 1;
+			else
+				index <<= 2;
+
+			return index;
+		}
+	}
+
+	return -EIO;
+}
+
+static int bcm2048_parse_rt_match_c(struct bcm2048_device *bdev, int i,
+					int index)
+{
+	int crc;
+
+	crc = bcm2048_rds_block_crc(bdev, i);
+
+	if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+		return 0;
+
+	BUG_ON((index+2) >= BCM2048_MAX_RDS_RT);
+
+	if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+		BCM2048_RDS_BLOCK_C) {
+		if (bdev->rds_info.rds_rt_group_b)
+			return 1;
+		bcm2048_parse_rds_rt_block(bdev, i, index, crc);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void bcm2048_parse_rt_match_d(struct bcm2048_device *bdev, int i,
+					int index)
+{
+	int crc;
+
+	crc = bcm2048_rds_block_crc(bdev, i);
+
+	if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+		return;
+
+	BUG_ON((index+4) >= BCM2048_MAX_RDS_RT);
+
+	if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+		BCM2048_RDS_BLOCK_D)
+		bcm2048_parse_rds_rt_block(bdev, i, index+2, crc);
+}
+
+static int bcm2048_parse_rds_rt(struct bcm2048_device *bdev)
+{
+	int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0;
+
+	for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
+
+		if (match_b) {
+			match_b = 0;
+			index = bcm2048_parse_rt_match_b(bdev, i);
+			if (index >= 0 && index <= (BCM2048_MAX_RDS_RT - 5))
+				match_c = 1;
+			continue;
+		} else if (match_c) {
+			match_c = 0;
+			if (bcm2048_parse_rt_match_c(bdev, i, index))
+				match_d = 1;
+			continue;
+		} else if (match_d) {
+			match_d = 0;
+			bcm2048_parse_rt_match_d(bdev, i, index);
+			continue;
+		}
+
+		/* Skip erroneous blocks due to messed up A block altogether */
+		if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK)
+			== BCM2048_RDS_BLOCK_A) {
+			crc = bcm2048_rds_block_crc(bdev, i);
+			if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+				continue;
+			/* Syncronize to a good RDS PI */
+			if (((bdev->rds_info.radio_text[i+1] << 8) +
+				bdev->rds_info.radio_text[i+2]) ==
+				bdev->rds_info.rds_pi)
+					match_b = 1;
+		}
+	}
+
+	return 0;
+}
+
+static void bcm2048_parse_rds_ps_block(struct bcm2048_device *bdev, int i,
+					int index, int crc)
+{
+	/* Good data will overwrite poor data */
+	if (crc) {
+		if (!bdev->rds_info.rds_ps[index])
+			bdev->rds_info.rds_ps[index] =
+				bdev->rds_info.radio_text[i+1];
+		if (!bdev->rds_info.rds_ps[index+1])
+			bdev->rds_info.rds_ps[index+1] =
+				bdev->rds_info.radio_text[i+2];
+	} else {
+		bdev->rds_info.rds_ps[index] = bdev->rds_info.radio_text[i+1];
+		bdev->rds_info.rds_ps[index+1] =
+			bdev->rds_info.radio_text[i+2];
+	}
+}
+
+static int bcm2048_parse_ps_match_c(struct bcm2048_device *bdev, int i,
+					int index)
+{
+	int crc;
+
+	crc = bcm2048_rds_block_crc(bdev, i);
+
+	if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+		return 0;
+
+	if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+		BCM2048_RDS_BLOCK_C)
+		return 1;
+
+	return 0;
+}
+
+static void bcm2048_parse_ps_match_d(struct bcm2048_device *bdev, int i,
+					int index)
+{
+	int crc;
+
+	crc = bcm2048_rds_block_crc(bdev, i);
+
+	if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+		return;
+
+	if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+		BCM2048_RDS_BLOCK_D)
+		bcm2048_parse_rds_ps_block(bdev, i, index, crc);
+}
+
+static int bcm2048_parse_ps_match_b(struct bcm2048_device *bdev, int i)
+{
+	int crc, index, ps_id, ps_group;
+
+	crc = bcm2048_rds_block_crc(bdev, i);
+
+	if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+		return -EIO;
+
+	/* Block B Radio PS match */
+	if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+		BCM2048_RDS_BLOCK_B) {
+		ps_id = bdev->rds_info.radio_text[i+1] &
+			BCM2048_RDS_BLOCK_MASK;
+		ps_group = bdev->rds_info.radio_text[i+1] &
+			BCM2048_RDS_GROUP_AB_MASK;
+
+		/*
+		 * Poor RSSI will lead to RDS data corruption
+		 * So using 3 (same) sequential values to justify major changes
+		 */
+		if (ps_group != bdev->rds_info.rds_ps_group) {
+			if (crc == BCM2048_RDS_CRC_NONE) {
+				bdev->rds_info.rds_ps_group_cnt++;
+				if (bdev->rds_info.rds_ps_group_cnt > 2) {
+					bdev->rds_info.rds_ps_group = ps_group;
+					bdev->rds_info.rds_ps_group_cnt	= 0;
+					dev_err(&bdev->client->dev,
+						"RDS PS Group change!\n");
+				} else {
+					return -EIO;
+				}
+			} else {
+				bdev->rds_info.rds_ps_group_cnt = 0;
+			}
+		}
+
+		if (ps_id == BCM2048_RDS_PS) {
+			index = bdev->rds_info.radio_text[i+2] &
+				BCM2048_RDS_PS_INDEX;
+			index <<= 1;
+			return index;
+		}
+	}
+
+	return -EIO;
+}
+
+static void bcm2048_parse_rds_ps(struct bcm2048_device *bdev)
+{
+	int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0;
+
+	for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
+
+		if (match_b) {
+			match_b = 0;
+			index = bcm2048_parse_ps_match_b(bdev, i);
+			if (index >= 0 && index < (BCM2048_MAX_RDS_PS - 1))
+				match_c = 1;
+			continue;
+		} else if (match_c) {
+			match_c = 0;
+			if (bcm2048_parse_ps_match_c(bdev, i, index))
+				match_d = 1;
+			continue;
+		} else if (match_d) {
+			match_d = 0;
+			bcm2048_parse_ps_match_d(bdev, i, index);
+			continue;
+		}
+
+		/* Skip erroneous blocks due to messed up A block altogether */
+		if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK)
+			== BCM2048_RDS_BLOCK_A) {
+			crc = bcm2048_rds_block_crc(bdev, i);
+			if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+				continue;
+			/* Syncronize to a good RDS PI */
+			if (((bdev->rds_info.radio_text[i+1] << 8) +
+				bdev->rds_info.radio_text[i+2]) ==
+				bdev->rds_info.rds_pi)
+					match_b = 1;
+		}
+	}
+}
+
+static void bcm2048_rds_fifo_receive(struct bcm2048_device *bdev)
+{
+	int err;
+
+	mutex_lock(&bdev->mutex);
+
+	err = bcm2048_recv_duples(bdev, BCM2048_I2C_RDS_DATA,
+				bdev->rds_info.radio_text, bdev->fifo_size);
+	if (err != 2) {
+		dev_err(&bdev->client->dev, "RDS Read problem\n");
+		return;
+	}
+
+	bdev->rds_info.text_len = bdev->fifo_size;
+
+	bcm2048_parse_rds_pi(bdev);
+	bcm2048_parse_rds_rt(bdev);
+	bcm2048_parse_rds_ps(bdev);
+
+	mutex_unlock(&bdev->mutex);
+}
+
+static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data)
+{
+	int err = 0, i, p = 0;
+	char data_buffer[BCM2048_MAX_RDS_RADIO_TEXT*5];
+
+	mutex_lock(&bdev->mutex);
+
+	if (!bdev->rds_info.text_len) {
+		err = -EINVAL;
+		goto unlock;
+	}
+
+	memset(data_buffer, 0, sizeof(data_buffer));
+
+	for (i = 0; i < bdev->rds_info.text_len; i++) {
+		p += sprintf(data_buffer+p, "%x ",
+			bdev->rds_info.radio_text[i]);
+	}
+
+	memcpy(data, data_buffer, p);
+
+unlock:
+	mutex_unlock(&bdev->mutex);
+	return err;
+}
+
+/*
+ *	BCM2048 default initialization sequence
+ */
+static int bcm2048_init(struct bcm2048_device *bdev)
+{
+	int err;
+
+	err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON);
+	if (err < 0)
+		goto exit;
+
+	err = bcm2048_set_audio_route(bdev, BCM2048_AUDIO_ROUTE_DAC);
+	if (err < 0)
+		goto exit;
+
+	err = bcm2048_set_dac_output(bdev, BCM2048_DAC_OUTPUT_LEFT |
+		BCM2048_DAC_OUTPUT_RIGHT);
+
+exit:
+	return err;
+}
+
+/*
+ *	BCM2048 default deinitialization sequence
+ */
+static int bcm2048_deinit(struct bcm2048_device *bdev)
+{
+	int err;
+
+	err = bcm2048_set_audio_route(bdev, 0);
+	if (err < 0)
+		goto exit;
+
+	err = bcm2048_set_dac_output(bdev, 0);
+	if (err < 0)
+		goto exit;
+
+	err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+	if (err < 0)
+		goto exit;
+
+exit:
+	return err;
+}
+
+/*
+ *	BCM2048 probe sequence
+ */
+static int bcm2048_probe(struct bcm2048_device *bdev)
+{
+	int err;
+
+	err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_checkrev(bdev);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_set_mute(bdev, BCM2048_DEFAULT_MUTE);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_set_region(bdev, BCM2048_DEFAULT_REGION);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_set_fm_search_rssi_threshold(bdev,
+					BCM2048_DEFAULT_RSSI_THRESHOLD);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_set_fm_automatic_stereo_mono(bdev, BCM2048_ITEM_ENABLED);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_get_rds_wline(bdev);
+	if (err < BCM2048_DEFAULT_RDS_WLINE)
+		err = bcm2048_set_rds_wline(bdev, BCM2048_DEFAULT_RDS_WLINE);
+	if (err < 0)
+		goto unlock;
+
+	err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+
+unlock:
+	return err;
+}
+
+/*
+ *	BCM2048 workqueue handler
+ */
+static void bcm2048_work(struct work_struct *work)
+{
+	struct bcm2048_device *bdev;
+	u8 flag_lsb, flag_msb, flags;
+
+	bdev = container_of(work, struct bcm2048_device, work);
+	bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &flag_lsb);
+	bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &flag_msb);
+
+	if (flag_lsb & (BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED |
+			BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)) {
+
+		if (flag_lsb & BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)
+			bdev->scan_state = BCM2048_SCAN_FAIL;
+		else
+			bdev->scan_state = BCM2048_SCAN_OK;
+
+		complete(&bdev->compl);
+	}
+
+	if (flag_msb & BCM2048_RDS_FLAG_FIFO_WLINE) {
+		bcm2048_rds_fifo_receive(bdev);
+		if (bdev->rds_state) {
+			flags =	BCM2048_RDS_FLAG_FIFO_WLINE;
+			bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+						flags);
+		}
+
+	}
+}
+
+/*
+ *	BCM2048 interrupt handler
+ */
+static irqreturn_t bcm2048_handler(int irq, void *dev)
+{
+	struct bcm2048_device *bdev = dev;
+
+	dev_dbg(&bdev->client->dev, "IRQ called, queuing work\n");
+	if (bdev->power_state)
+		schedule_work(&bdev->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ *	BCM2048 sysfs interface definitions
+ */
+#define property_write(prop, type, mask, check)				\
+static ssize_t bcm2048_##prop##_write(struct device *dev,		\
+					struct device_attribute *attr,	\
+					const char *buf,		\
+					size_t count)			\
+{									\
+	struct bcm2048_device *bdev = dev_get_drvdata(dev);		\
+	type value;							\
+	int err;							\
+									\
+	if (!bdev)							\
+		return -ENODEV;						\
+									\
+	sscanf(buf, mask, &value);					\
+									\
+	if (check)							\
+		return -EDOM;						\
+									\
+	err = bcm2048_set_##prop(bdev, value);				\
+									\
+	return err < 0 ? err : count;					\
+}
+
+#define property_read(prop, size, mask)					\
+static ssize_t bcm2048_##prop##_read(struct device *dev,		\
+					struct device_attribute *attr,	\
+					char *buf)			\
+{									\
+	struct bcm2048_device *bdev = dev_get_drvdata(dev);		\
+	size value;							\
+									\
+	if (!bdev)							\
+		return -ENODEV;						\
+									\
+	value = bcm2048_get_##prop(bdev);				\
+									\
+	if (value >= 0)							\
+		value = sprintf(buf, mask "\n", value);			\
+									\
+	return value;							\
+}
+
+#define property_signed_read(prop, size, mask)				\
+static ssize_t bcm2048_##prop##_read(struct device *dev,		\
+					struct device_attribute *attr,	\
+					char *buf)			\
+{									\
+	struct bcm2048_device *bdev = dev_get_drvdata(dev);		\
+	size value;							\
+									\
+	if (!bdev)							\
+		return -ENODEV;						\
+									\
+	value = bcm2048_get_##prop(bdev);				\
+									\
+	value = sprintf(buf, mask "\n", value);				\
+									\
+	return value;							\
+}
+
+#define DEFINE_SYSFS_PROPERTY(prop, signal, size, mask, check)		\
+property_write(prop, signal size, mask, check)				\
+property_read(prop, size, mask)
+
+#define property_str_read(prop, size)					\
+static ssize_t bcm2048_##prop##_read(struct device *dev,		\
+					struct device_attribute *attr,	\
+					char *buf)			\
+{									\
+	struct bcm2048_device *bdev = dev_get_drvdata(dev);		\
+	int count;							\
+	u8 *out;							\
+									\
+	if (!bdev)							\
+		return -ENODEV;						\
+									\
+	out = kzalloc(size + 1, GFP_KERNEL);				\
+	if (!out)							\
+		return -ENOMEM;						\
+									\
+	bcm2048_get_##prop(bdev, out);					\
+	count = sprintf(buf, "%s\n", out);				\
+									\
+	kfree(out);							\
+									\
+	return count;							\
+}
+
+DEFINE_SYSFS_PROPERTY(power_state, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(mute, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(audio_route, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(dac_output, unsigned, int, "%u", 0)
+
+DEFINE_SYSFS_PROPERTY(fm_hi_lo_injection, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_frequency, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_af_frequency, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_deemphasis, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_rds_mask, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_best_tune_mode, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_search_rssi_threshold, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_search_mode_direction, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_search_tune_mode, unsigned, int, "%u", value > 3)
+
+DEFINE_SYSFS_PROPERTY(rds, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_b_block_mask, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_b_block_match, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_pi_mask, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_pi_match, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_wline, unsigned, int, "%u", 0)
+property_read(rds_pi, unsigned int, "%x")
+property_str_read(rds_rt, (BCM2048_MAX_RDS_RT + 1))
+property_str_read(rds_ps, (BCM2048_MAX_RDS_PS + 1))
+
+property_read(fm_rds_flags, unsigned int, "%u")
+property_str_read(rds_data, BCM2048_MAX_RDS_RADIO_TEXT*5)
+
+property_read(region_bottom_frequency, unsigned int, "%u")
+property_read(region_top_frequency, unsigned int, "%u")
+property_signed_read(fm_carrier_error, int, "%d")
+property_signed_read(fm_rssi, int, "%d")
+DEFINE_SYSFS_PROPERTY(region, unsigned, int, "%u", 0)
+
+static struct device_attribute attrs[] = {
+	__ATTR(power_state, S_IRUGO | S_IWUSR, bcm2048_power_state_read,
+		bcm2048_power_state_write),
+	__ATTR(mute, S_IRUGO | S_IWUSR, bcm2048_mute_read,
+		bcm2048_mute_write),
+	__ATTR(audio_route, S_IRUGO | S_IWUSR, bcm2048_audio_route_read,
+		bcm2048_audio_route_write),
+	__ATTR(dac_output, S_IRUGO | S_IWUSR, bcm2048_dac_output_read,
+		bcm2048_dac_output_write),
+	__ATTR(fm_hi_lo_injection, S_IRUGO | S_IWUSR,
+		bcm2048_fm_hi_lo_injection_read,
+		bcm2048_fm_hi_lo_injection_write),
+	__ATTR(fm_frequency, S_IRUGO | S_IWUSR, bcm2048_fm_frequency_read,
+		bcm2048_fm_frequency_write),
+	__ATTR(fm_af_frequency, S_IRUGO | S_IWUSR,
+		bcm2048_fm_af_frequency_read,
+		bcm2048_fm_af_frequency_write),
+	__ATTR(fm_deemphasis, S_IRUGO | S_IWUSR, bcm2048_fm_deemphasis_read,
+		bcm2048_fm_deemphasis_write),
+	__ATTR(fm_rds_mask, S_IRUGO | S_IWUSR, bcm2048_fm_rds_mask_read,
+		bcm2048_fm_rds_mask_write),
+	__ATTR(fm_best_tune_mode, S_IRUGO | S_IWUSR,
+		bcm2048_fm_best_tune_mode_read,
+		bcm2048_fm_best_tune_mode_write),
+	__ATTR(fm_search_rssi_threshold, S_IRUGO | S_IWUSR,
+		bcm2048_fm_search_rssi_threshold_read,
+		bcm2048_fm_search_rssi_threshold_write),
+	__ATTR(fm_search_mode_direction, S_IRUGO | S_IWUSR,
+		bcm2048_fm_search_mode_direction_read,
+		bcm2048_fm_search_mode_direction_write),
+	__ATTR(fm_search_tune_mode, S_IRUGO | S_IWUSR,
+		bcm2048_fm_search_tune_mode_read,
+		bcm2048_fm_search_tune_mode_write),
+	__ATTR(rds, S_IRUGO | S_IWUSR, bcm2048_rds_read,
+		bcm2048_rds_write),
+	__ATTR(rds_b_block_mask, S_IRUGO | S_IWUSR,
+		bcm2048_rds_b_block_mask_read,
+		bcm2048_rds_b_block_mask_write),
+	__ATTR(rds_b_block_match, S_IRUGO | S_IWUSR,
+		bcm2048_rds_b_block_match_read,
+		bcm2048_rds_b_block_match_write),
+	__ATTR(rds_pi_mask, S_IRUGO | S_IWUSR, bcm2048_rds_pi_mask_read,
+		bcm2048_rds_pi_mask_write),
+	__ATTR(rds_pi_match, S_IRUGO | S_IWUSR, bcm2048_rds_pi_match_read,
+		bcm2048_rds_pi_match_write),
+	__ATTR(rds_wline, S_IRUGO | S_IWUSR, bcm2048_rds_wline_read,
+		bcm2048_rds_wline_write),
+	__ATTR(rds_pi, S_IRUGO, bcm2048_rds_pi_read, NULL),
+	__ATTR(rds_rt, S_IRUGO, bcm2048_rds_rt_read, NULL),
+	__ATTR(rds_ps, S_IRUGO, bcm2048_rds_ps_read, NULL),
+	__ATTR(fm_rds_flags, S_IRUGO, bcm2048_fm_rds_flags_read, NULL),
+	__ATTR(region_bottom_frequency, S_IRUGO,
+		bcm2048_region_bottom_frequency_read, NULL),
+	__ATTR(region_top_frequency, S_IRUGO,
+		bcm2048_region_top_frequency_read, NULL),
+	__ATTR(fm_carrier_error, S_IRUGO,
+		bcm2048_fm_carrier_error_read, NULL),
+	__ATTR(fm_rssi, S_IRUGO,
+		bcm2048_fm_rssi_read, NULL),
+	__ATTR(region, S_IRUGO | S_IWUSR, bcm2048_region_read,
+		bcm2048_region_write),
+	__ATTR(rds_data, S_IRUGO, bcm2048_rds_data_read, NULL),
+};
+
+static int bcm2048_sysfs_unregister_properties(struct bcm2048_device *bdev,
+						int size)
+{
+	int i;
+
+	for (i = 0; i < size; i++)
+		device_remove_file(&bdev->client->dev, &attrs[i]);
+
+	return 0;
+}
+
+static int bcm2048_sysfs_register_properties(struct bcm2048_device *bdev)
+{
+	int err = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(attrs); i++) {
+		if (device_create_file(&bdev->client->dev, &attrs[i]) != 0) {
+			dev_err(&bdev->client->dev,
+					"could not register sysfs entry\n");
+			err = -EBUSY;
+			bcm2048_sysfs_unregister_properties(bdev, i);
+			break;
+		}
+	}
+
+	return err;
+}
+
+/*
+ *	bcm2048_fops - file operations interface
+ */
+static const struct v4l2_file_operations bcm2048_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= video_ioctl2,
+};
+
+/*
+ *	Video4Linux Interface
+ */
+static struct v4l2_queryctrl bcm2048_v4l2_queryctrl[] = {
+	{
+		.id		= V4L2_CID_AUDIO_VOLUME,
+		.flags		= V4L2_CTRL_FLAG_DISABLED,
+	},
+	{
+		.id		= V4L2_CID_AUDIO_BALANCE,
+		.flags		= V4L2_CTRL_FLAG_DISABLED,
+	},
+	{
+		.id		= V4L2_CID_AUDIO_BASS,
+		.flags		= V4L2_CTRL_FLAG_DISABLED,
+	},
+	{
+		.id		= V4L2_CID_AUDIO_TREBLE,
+		.flags		= V4L2_CTRL_FLAG_DISABLED,
+	},
+	{
+		.id		= V4L2_CID_AUDIO_MUTE,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+		.name		= "Mute",
+		.minimum	= 0,
+		.maximum	= 1,
+		.step		= 1,
+		.default_value	= 1,
+	},
+	{
+		.id		= V4L2_CID_AUDIO_LOUDNESS,
+		.flags		= V4L2_CTRL_FLAG_DISABLED,
+	},
+};
+
+static int bcm2048_vidioc_querycap(struct file *file, void *priv,
+		struct v4l2_capability *capability)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+
+	strlcpy(capability->driver, BCM2048_DRIVER_NAME,
+		sizeof(capability->driver));
+	strlcpy(capability->card, BCM2048_DRIVER_CARD,
+		sizeof(capability->card));
+	snprintf(capability->bus_info, 32, "I2C: 0x%X", bdev->client->addr);
+	capability->version = BCM2048_DRIVER_VERSION;
+	capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
+					V4L2_CAP_HW_FREQ_SEEK;
+
+	return 0;
+}
+
+static int bcm2048_vidioc_g_input(struct file *filp, void *priv,
+		unsigned int *i)
+{
+	*i = 0;
+
+	return 0;
+}
+
+static int bcm2048_vidioc_s_input(struct file *filp, void *priv,
+					unsigned int i)
+{
+	if (i)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int bcm2048_vidioc_queryctrl(struct file *file, void *priv,
+		struct v4l2_queryctrl *qc)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bcm2048_v4l2_queryctrl); i++) {
+		if (qc->id && qc->id == bcm2048_v4l2_queryctrl[i].id) {
+			memcpy(qc, &(bcm2048_v4l2_queryctrl[i]), sizeof(*qc));
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int bcm2048_vidioc_g_ctrl(struct file *file, void *priv,
+		struct v4l2_control *ctrl)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+	int err = 0;
+
+	if (!bdev)
+		return -ENODEV;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		err = bcm2048_get_mute(bdev);
+		if (err >= 0)
+			ctrl->value = err;
+		break;
+	}
+
+	return err;
+}
+
+static int bcm2048_vidioc_s_ctrl(struct file *file, void *priv,
+		struct v4l2_control *ctrl)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+	int err = 0;
+
+	if (!bdev)
+		return -ENODEV;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		if (ctrl->value) {
+			if (bdev->power_state) {
+				err = bcm2048_set_mute(bdev, ctrl->value);
+				err |= bcm2048_deinit(bdev);
+			}
+		} else {
+			if (!bdev->power_state) {
+				err = bcm2048_init(bdev);
+				err |= bcm2048_set_mute(bdev, ctrl->value);
+			}
+		}
+		break;
+	}
+
+	return err;
+}
+
+static int bcm2048_vidioc_g_audio(struct file *file, void *priv,
+		struct v4l2_audio *audio)
+{
+	if (audio->index > 1)
+		return -EINVAL;
+
+	strncpy(audio->name, "Radio", 32);
+	audio->capability = V4L2_AUDCAP_STEREO;
+
+	return 0;
+}
+
+static int bcm2048_vidioc_s_audio(struct file *file, void *priv,
+		struct v4l2_audio *audio)
+{
+	if (audio->index != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int bcm2048_vidioc_g_tuner(struct file *file, void *priv,
+		struct v4l2_tuner *tuner)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+	s8 f_error;
+	s8 rssi;
+
+	if (!bdev)
+		return -ENODEV;
+
+	if (tuner->index > 0)
+		return -EINVAL;
+
+	strncpy(tuner->name, "FM Receiver", 32);
+	tuner->type = V4L2_TUNER_RADIO;
+	tuner->rangelow =
+		dev_to_v4l2(bcm2048_get_region_bottom_frequency(bdev));
+	tuner->rangehigh =
+		dev_to_v4l2(bcm2048_get_region_top_frequency(bdev));
+	tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
+	tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW;
+	tuner->audmode = V4L2_TUNER_MODE_STEREO;
+	tuner->afc = 0;
+	if (bdev->power_state) {
+		/*
+		 * Report frequencies with high carrier errors to have zero
+		 * signal level
+		 */
+		f_error = bcm2048_get_fm_carrier_error(bdev);
+		if (f_error < BCM2048_FREQ_ERROR_FLOOR ||
+		    f_error > BCM2048_FREQ_ERROR_ROOF) {
+			tuner->signal = 0;
+		} else {
+			/*
+			 * RSSI level -60 dB is defined to report full
+			 * signal strenght
+			 */
+			rssi = bcm2048_get_fm_rssi(bdev);
+			if (rssi >= BCM2048_RSSI_LEVEL_BASE) {
+				tuner->signal = 0xFFFF;
+			} else if (rssi > BCM2048_RSSI_LEVEL_ROOF) {
+				tuner->signal = (rssi +
+						 BCM2048_RSSI_LEVEL_ROOF_NEG)
+						 * BCM2048_SIGNAL_MULTIPLIER;
+			} else {
+				tuner->signal = 0;
+			}
+		}
+	} else {
+		tuner->signal = 0;
+	}
+
+	return 0;
+}
+
+static int bcm2048_vidioc_s_tuner(struct file *file, void *priv,
+		struct v4l2_tuner *tuner)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+
+	if (!bdev)
+		return -ENODEV;
+
+	if (tuner->index > 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int bcm2048_vidioc_g_frequency(struct file *file, void *priv,
+		struct v4l2_frequency *freq)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+	int err = 0;
+	int f;
+
+	if (!bdev->power_state)
+		return -ENODEV;
+
+	freq->type = V4L2_TUNER_RADIO;
+	f = bcm2048_get_fm_frequency(bdev);
+
+	if (f < 0)
+		err = f;
+	else
+		freq->frequency = dev_to_v4l2(f);
+
+	return err;
+}
+
+static int bcm2048_vidioc_s_frequency(struct file *file, void *priv,
+		struct v4l2_frequency *freq)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+	int err;
+
+	if (freq->type != V4L2_TUNER_RADIO)
+		return -EINVAL;
+
+	if (!bdev->power_state)
+		return -ENODEV;
+
+	err = bcm2048_set_fm_frequency(bdev, v4l2_to_dev(freq->frequency));
+	err |= bcm2048_set_fm_search_tune_mode(bdev, BCM2048_FM_PRE_SET_MODE);
+
+	return err;
+}
+
+static int bcm2048_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+						struct v4l2_hw_freq_seek *seek)
+{
+	struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+	int err;
+
+	if (!bdev->power_state)
+		return -ENODEV;
+
+	if ((seek->tuner != 0) || (seek->type != V4L2_TUNER_RADIO))
+		return -EINVAL;
+
+	err = bcm2048_set_fm_search_mode_direction(bdev, seek->seek_upward);
+	err |= bcm2048_set_fm_search_tune_mode(bdev,
+			BCM2048_FM_AUTO_SEARCH_MODE);
+
+	return err;
+}
+
+static struct v4l2_ioctl_ops bcm2048_ioctl_ops = {
+	.vidioc_querycap	= bcm2048_vidioc_querycap,
+	.vidioc_g_input		= bcm2048_vidioc_g_input,
+	.vidioc_s_input		= bcm2048_vidioc_s_input,
+	.vidioc_queryctrl	= bcm2048_vidioc_queryctrl,
+	.vidioc_g_ctrl		= bcm2048_vidioc_g_ctrl,
+	.vidioc_s_ctrl		= bcm2048_vidioc_s_ctrl,
+	.vidioc_g_audio		= bcm2048_vidioc_g_audio,
+	.vidioc_s_audio		= bcm2048_vidioc_s_audio,
+	.vidioc_g_tuner		= bcm2048_vidioc_g_tuner,
+	.vidioc_s_tuner		= bcm2048_vidioc_s_tuner,
+	.vidioc_g_frequency	= bcm2048_vidioc_g_frequency,
+	.vidioc_s_frequency	= bcm2048_vidioc_s_frequency,
+	.vidioc_s_hw_freq_seek  = bcm2048_vidioc_s_hw_freq_seek,
+};
+
+/*
+ * bcm2048_viddev_template - video device interface
+ */
+static struct video_device bcm2048_viddev_template = {
+	.fops			= &bcm2048_fops,
+	.name			= BCM2048_DRIVER_NAME,
+	.release		= video_device_release,
+	.ioctl_ops		= &bcm2048_ioctl_ops,
+};
+
+/*
+ *	I2C driver interface
+ */
+static int bcm2048_i2c_driver_probe(struct i2c_client *client,
+					const struct i2c_device_id *id)
+{
+	struct bcm2048_device *bdev;
+	int err, skip_release = 0;
+
+	bdev = kzalloc(sizeof *bdev, GFP_KERNEL);
+	if (!bdev) {
+		dev_dbg(&client->dev, "Failed to alloc video device.\n");
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	bdev->videodev = video_device_alloc();
+	if (!bdev->videodev) {
+		dev_dbg(&client->dev, "Failed to alloc video device.\n");
+		err = -ENOMEM;
+		goto free_bdev;
+	}
+
+	bdev->client = client;
+	i2c_set_clientdata(client, bdev);
+	mutex_init(&bdev->mutex);
+	init_completion(&bdev->compl);
+	INIT_WORK(&bdev->work, bcm2048_work);
+
+	if (client->irq) {
+		err = request_irq(client->irq,
+			bcm2048_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED,
+			client->name, bdev);
+		if (err < 0) {
+			dev_err(&client->dev, "Could not request IRQ\n");
+			goto free_vdev;
+		}
+		dev_dbg(&client->dev, "IRQ requested.\n");
+	} else {
+		dev_dbg(&client->dev, "IRQ not configured. Using timeouts.\n");
+	}
+
+	memcpy(bdev->videodev, &bcm2048_viddev_template,
+			sizeof(bcm2048_viddev_template));
+	video_set_drvdata(bdev->videodev, bdev);
+	if (video_register_device(bdev->videodev, VFL_TYPE_RADIO, radio_nr)) {
+		dev_dbg(&client->dev, "Could not register video device.\n");
+		err = -EIO;
+		goto free_irq;
+	}
+
+	err = bcm2048_sysfs_register_properties(bdev);
+	if (err < 0) {
+		dev_dbg(&client->dev, "Could not register sysfs interface.\n");
+		goto free_registration;
+	}
+
+	err = bcm2048_probe(bdev);
+	if (err < 0) {
+		dev_dbg(&client->dev, "Failed to probe device information.\n");
+		goto free_sysfs;
+	}
+
+	return 0;
+
+free_sysfs:
+	bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs));
+free_registration:
+	video_unregister_device(bdev->videodev);
+	/* video_unregister_device frees bdev->videodev */
+	bdev->videodev = NULL;
+	skip_release = 1;
+free_irq:
+	if (client->irq)
+		free_irq(client->irq, bdev);
+free_vdev:
+	if (!skip_release)
+		video_device_release(bdev->videodev);
+	i2c_set_clientdata(client, NULL);
+free_bdev:
+	kfree(bdev);
+exit:
+	return err;
+}
+
+static int __exit bcm2048_i2c_driver_remove(struct i2c_client *client)
+{
+	struct bcm2048_device *bdev = i2c_get_clientdata(client);
+	struct video_device *vd;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	if (bdev) {
+		vd = bdev->videodev;
+
+		bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs));
+
+		if (vd)
+			video_unregister_device(vd);
+
+		if (bdev->power_state)
+			bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+
+		if (client->irq > 0)
+			free_irq(client->irq, bdev);
+
+		cancel_work_sync(&bdev->work);
+
+		kfree(bdev);
+	}
+
+	i2c_set_clientdata(client, NULL);
+
+	return 0;
+}
+
+/*
+ *	bcm2048_i2c_driver - i2c driver interface
+ */
+static const struct i2c_device_id bcm2048_id[] = {
+	{ "bcm2048" , 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, bcm2048_id);
+
+static struct i2c_driver bcm2048_i2c_driver = {
+	.driver		= {
+		.name	= BCM2048_DRIVER_NAME,
+	},
+	.probe		= bcm2048_i2c_driver_probe,
+	.remove		= __exit_p(bcm2048_i2c_driver_remove),
+	.id_table	= bcm2048_id,
+};
+
+/*
+ *	Module Interface
+ */
+static int __init bcm2048_module_init(void)
+{
+	printk(KERN_INFO BCM2048_DRIVER_DESC "\n");
+
+	return i2c_add_driver(&bcm2048_i2c_driver);
+}
+module_init(bcm2048_module_init);
+
+static void __exit bcm2048_module_exit(void)
+{
+	i2c_del_driver(&bcm2048_i2c_driver);
+}
+module_exit(bcm2048_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(BCM2048_DRIVER_AUTHOR);
+MODULE_DESCRIPTION(BCM2048_DRIVER_DESC);
+MODULE_VERSION("0.0.1");
diff --git a/include/media/radio-bcm2048.h b/include/media/radio-bcm2048.h
new file mode 100644
index 0000000..61f52aa
--- /dev/null
+++ b/include/media/radio-bcm2048.h
@@ -0,0 +1,30 @@ 
+/*
+ * drivers/media/radio/radio-bcm2048.h
+ *
+ * Property and command definitions for bcm2048 radio receiver chip.
+ *
+ * Copyright (C) Nokia Corporation
+ * Contact: Eero Nurkkala <ext-eero.nurkkala@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 BCM2048_H
+#define BCM2048_H
+
+#define BCM2048_NAME		"bcm2048"
+#define BCM2048_I2C_ADDR	0x22
+
+#endif	/* ifndef BCM2048_H */