--- /dev/null
+/*
+ * drivers/staging/media/radio-bcm2048.c
+ *
+ * Driver for I2C Broadcom BCM2048 FM Radio Receiver:
+ *
+ * Copyright (C) Nokia Corporation
+ * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
+ *
+ * Copyright (C) Nils Faerber <nils.faerber@kernelconcepts.de>
+ *
+ * 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
+ */
+
+/*
+ * History:
+ *             Eero Nurkkala <ext-eero.nurkkala@nokia.com>
+ *             Version 0.0.1
+ *             - Initial implementation
+ * 2010-02-21  Nils Faerber <nils.faerber@kernelconcepts.de>
+ *             Version 0.0.2
+ *             - Add support for interrupt driven rds data reading
+ */
+
+#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 <linux/slab.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include "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;
+
+       /* for rds data device read */
+       wait_queue_head_t read_queue;
+       unsigned int users;
+       unsigned char rds_data_available;
+       unsigned int rd_index;
+};
+
+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, ®ion_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);
+
+       wake_up_interruptible(&bdev->read_queue);
+}
+
+static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data)
+{
+       int err = 0, i, p = 0;
+       char *data_buffer;
+
+       mutex_lock(&bdev->mutex);
+
+       if (!bdev->rds_info.text_len) {
+               err = -EINVAL;
+               goto unlock;
+       }
+
+       data_buffer = kzalloc(BCM2048_MAX_RDS_RADIO_TEXT*5, GFP_KERNEL);
+       if (!data_buffer) {
+               err = -ENOMEM;
+               goto unlock;
+       }
+
+       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);
+       kfree(data_buffer);
+
+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);
+
+       init_waitqueue_head(&bdev->read_queue);
+       bdev->rds_data_available = 0;
+       bdev->rd_index = 0;
+       bdev->users = 0;
+
+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);
+               }
+               bdev->rds_data_available = 1;
+               bdev->rd_index = 0; /* new data, new start */
+       }
+}
+
+/*
+ *     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;
+}
+
+
+static int bcm2048_fops_open(struct file *file)
+{
+       struct bcm2048_device *bdev = video_drvdata(file);
+
+       bdev->users++;
+       bdev->rd_index = 0;
+       bdev->rds_data_available = 0;
+
+       return 0;
+}
+
+static int bcm2048_fops_release(struct file *file)
+{
+       struct bcm2048_device *bdev = video_drvdata(file);
+
+       bdev->users--;
+
+       return 0;
+}
+
+static unsigned int bcm2048_fops_poll(struct file *file,
+               struct poll_table_struct *pts)
+{
+       struct bcm2048_device *bdev = video_drvdata(file);
+       int retval = 0;
+
+       poll_wait(file, &bdev->read_queue, pts);
+
+       if (bdev->rds_data_available)
+               retval = POLLIN | POLLRDNORM;
+
+       return retval;
+}
+
+static ssize_t bcm2048_fops_read(struct file *file, char __user *buf,
+       size_t count, loff_t *ppos)
+{
+       struct bcm2048_device *bdev = video_drvdata(file);
+       int i;
+       int retval = 0;
+
+       /* we return at least 3 bytes, one block */
+       count = (count / 3) * 3; /* only multiples of 3 */
+       if (count < 3)
+               return -ENOBUFS;
+
+       while (!bdev->rds_data_available) {
+               if (file->f_flags & O_NONBLOCK) {
+                       retval = -EWOULDBLOCK;
+                       goto done;
+               }
+               /* interruptible_sleep_on(&bdev->read_queue); */
+               if (wait_event_interruptible(bdev->read_queue,
+                       bdev->rds_data_available) < 0) {
+                       retval = -EINTR;
+                       goto done;
+               }
+       }
+
+       mutex_lock(&bdev->mutex);
+       /* copy data to userspace */
+       i = bdev->fifo_size - bdev->rd_index;
+       if (count > i)
+               count = (i / 3) * 3;
+
+       i = 0;
+       while (i < count) {
+               unsigned char tmpbuf[3];
+               tmpbuf[i] = bdev->rds_info.radio_text[bdev->rd_index+i+2];
+               tmpbuf[i+1] = bdev->rds_info.radio_text[bdev->rd_index+i+1];
+               tmpbuf[i+2] = ((bdev->rds_info.radio_text[bdev->rd_index+i]
+                               & 0xf0) >> 4);
+               if ((bdev->rds_info.radio_text[bdev->rd_index+i] &
+                       BCM2048_RDS_CRC_MASK) == BCM2048_RDS_CRC_UNRECOVARABLE)
+                       tmpbuf[i+2] |= 0x80;
+               if (copy_to_user(buf+i, tmpbuf, 3)) {
+                       retval = -EFAULT;
+                       break;
+               };
+               i += 3;
+       }
+
+       bdev->rd_index += i;
+       if (bdev->rd_index >= bdev->fifo_size)
+               bdev->rds_data_available = 0;
+
+       mutex_unlock(&bdev->mutex);
+       if (retval == 0)
+               retval = i;
+
+done:
+       return retval;
+}
+
+/*
+ *     bcm2048_fops - file operations interface
+ */
+static const struct v4l2_file_operations bcm2048_fops = {
+       .owner          = THIS_MODULE,
+       .ioctl          = video_ioctl2,
+       /* for RDS read support */
+       .open           = bcm2048_fops_open,
+       .release        = bcm2048_fops_release,
+       .read           = bcm2048_fops_read,
+       .poll           = bcm2048_fops_poll
+};
+
+/*
+ *     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,
+               const 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,
+               const 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,
+               const 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,
+                                       const 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)
+{
+       pr_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.2");