summaryrefslogtreecommitdiff
path: root/drivers/media/radio/stfm1000/stfm1000-i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/radio/stfm1000/stfm1000-i2c.c')
-rw-r--r--drivers/media/radio/stfm1000/stfm1000-i2c.c453
1 files changed, 453 insertions, 0 deletions
diff --git a/drivers/media/radio/stfm1000/stfm1000-i2c.c b/drivers/media/radio/stfm1000/stfm1000-i2c.c
new file mode 100644
index 000000000000..50a047ecfda6
--- /dev/null
+++ b/drivers/media/radio/stfm1000/stfm1000-i2c.c
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/io.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+
+#include "stfm1000.h"
+
+#define stfm1000_i2c_debug(p, fmt, arg...) \
+ do { \
+ if ((p)->dbgflg & STFM1000_DBGFLG_I2C) \
+ printk(KERN_INFO "stfm1000: " fmt, ##arg); \
+ } while (0)
+
+static const char *reg_names[STFM1000_NUM_REGS] = {
+#undef REGNAME
+#define REGNAME(x) \
+ [STFM1000_ ## x / 4] = #x ""
+
+ REGNAME(TUNE1),
+ REGNAME(SDNOMINAL),
+ REGNAME(PILOTTRACKING),
+ REGNAME(INITIALIZATION1),
+ REGNAME(INITIALIZATION2),
+ REGNAME(INITIALIZATION3),
+ REGNAME(INITIALIZATION4),
+ REGNAME(INITIALIZATION5),
+ REGNAME(INITIALIZATION6),
+ REGNAME(REF),
+ REGNAME(LNA),
+ REGNAME(MIXFILT),
+ REGNAME(CLK1),
+ REGNAME(CLK2),
+ REGNAME(ADC),
+ REGNAME(AGC_CONTROL1),
+ REGNAME(AGC_CONTROL2),
+ REGNAME(DATAPATH),
+ REGNAME(RMS),
+ REGNAME(AGC_STAT),
+ REGNAME(SIGNALQUALITY),
+ REGNAME(DCEST),
+ REGNAME(RSSI_TONE),
+ REGNAME(PILOTCORRECTION),
+ REGNAME(ATTENTION),
+ REGNAME(CLK3),
+ REGNAME(CHIPID),
+#undef REGNAME
+};
+
+static const int stfm1000_rw_regs[] = {
+ STFM1000_TUNE1,
+ STFM1000_SDNOMINAL,
+ STFM1000_PILOTTRACKING,
+ STFM1000_INITIALIZATION1,
+ STFM1000_INITIALIZATION2,
+ STFM1000_INITIALIZATION3,
+ STFM1000_INITIALIZATION4,
+ STFM1000_INITIALIZATION5,
+ STFM1000_INITIALIZATION6,
+ STFM1000_REF,
+ STFM1000_LNA,
+ STFM1000_MIXFILT,
+ STFM1000_CLK1,
+ STFM1000_CLK2,
+ STFM1000_ADC,
+ STFM1000_AGC_CONTROL1,
+ STFM1000_AGC_CONTROL2,
+ STFM1000_DATAPATH,
+ STFM1000_ATTENTION, /* it's both WR/RD */
+};
+
+static const int stfm1000_ra_regs[] = {
+ STFM1000_RMS,
+ STFM1000_AGC_STAT,
+ STFM1000_SIGNALQUALITY,
+ STFM1000_DCEST,
+ STFM1000_RSSI_TONE,
+ STFM1000_PILOTCORRECTION,
+ STFM1000_ATTENTION, /* it's both WR/RD - always read */
+ STFM1000_CLK3,
+ STFM1000_CHIPID
+};
+
+static int verify_writes;
+
+void stfm1000_setup_reg_set(struct stfm1000 *stfm1000)
+{
+ int i, reg;
+
+ /* set up register sets (read/write) */
+ for (i = 0; i < ARRAY_SIZE(stfm1000_rw_regs); i++) {
+ reg = stfm1000_rw_regs[i] / 4;
+ stfm1000->reg_rw_set[reg / 32] |= 1U << (reg & 31);
+ /* printk(KERN_INFO "STFM1000: rw <= %d\n", reg); */
+ }
+
+ /* for (i = 0; i < ARRAY_SIZE(stfm1000->reg_rw_set); i++)
+ printk("RW[%d] = 0x%08x\n", i, stfm1000->reg_rw_set[i]); */
+
+ /* set up register sets (read only) */
+ for (i = 0; i < ARRAY_SIZE(stfm1000_ra_regs); i++) {
+ reg = stfm1000_ra_regs[i] / 4;
+ stfm1000->reg_ra_set[reg / 32] |= 1U << (reg & 31);
+ /* printk(KERN_INFO "STFM1000: rw <= %d\n", reg); */
+ }
+ /* for (i = 0; i < ARRAY_SIZE(stfm1000->reg_ra_set); i++)
+ printk("RO[%d] = 0x%08x\n", i, stfm1000->reg_ra_set[i]); */
+
+ /* clear dirty */
+ memset(stfm1000->reg_dirty_set, 0, sizeof(stfm1000->reg_dirty_set));
+}
+
+static int stfm1000_reg_is_rw(struct stfm1000 *stfm1000, int reg)
+{
+ reg >>= 2;
+ return !!(stfm1000->reg_rw_set[reg / 32] & (1 << (reg & 31)));
+}
+
+static int stfm1000_reg_is_ra(struct stfm1000 *stfm1000, int reg)
+{
+ reg >>= 2;
+ return !!(stfm1000->reg_ra_set[reg / 32] & (1 << (reg & 31)));
+}
+
+static int stfm1000_reg_is_dirty(struct stfm1000 *stfm1000, int reg)
+{
+ reg >>= 2;
+ return !!(stfm1000->reg_dirty_set[reg / 32] & (1 << (reg & 31)));
+}
+
+static void stfm1000_reg_set_dirty(struct stfm1000 *stfm1000, int reg)
+{
+ reg >>= 2;
+ stfm1000->reg_dirty_set[reg / 32] |= 1 << (reg & 31);
+}
+
+static inline int stfm1000_reg_is_writeable(struct stfm1000 *stfm1000, int reg)
+{
+ return stfm1000_reg_is_rw(stfm1000, reg);
+}
+
+static inline int stfm1000_reg_is_readable(struct stfm1000 *stfm1000, int reg)
+{
+ return stfm1000_reg_is_rw(stfm1000, reg) ||
+ stfm1000_reg_is_ra(stfm1000, reg);
+}
+
+/********************************************************/
+
+static int write_reg_internal(struct stfm1000 *stfm1000, int reg, u32 value)
+{
+ u8 values[5];
+ int ret;
+
+ stfm1000_i2c_debug(stfm1000, "%s(%s - 0x%02x, 0x%08x)\n", __func__,
+ reg_names[reg / 4], reg, value);
+
+ values[0] = (u8)reg;
+ values[1] = (u8)value;
+ values[2] = (u8)(value >> 8);
+ values[3] = (u8)(value >> 16);
+ values[4] = (u8)(value >> 24);
+ ret = i2c_master_send(stfm1000->client, values, 5);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int read_reg_internal(struct stfm1000 *stfm1000, int reg, u32 *value)
+{
+ u8 regb = reg;
+ u8 values[4];
+ int ret;
+
+ ret = i2c_master_send(stfm1000->client, &regb, 1);
+ if (ret < 0)
+ goto out;
+ ret = i2c_master_recv(stfm1000->client, values, 4);
+ if (ret < 0)
+ goto out;
+ *value = (u32)values[0] | ((u32)values[1] << 8) |
+ ((u32)values[2] << 16) | ((u32)values[3] << 24);
+ ret = 0;
+
+ stfm1000_i2c_debug(stfm1000, "%s(%s - 0x%02x, 0x%08x)\n", __func__,
+ reg_names[reg / 4], reg, *value);
+out:
+ return ret;
+}
+
+int stfm1000_raw_write(struct stfm1000 *stfm1000, int reg, u32 value)
+{
+ int ret;
+
+ mutex_lock(&stfm1000->xfer_lock);
+ ret = write_reg_internal(stfm1000, reg, value);
+ mutex_unlock(&stfm1000->xfer_lock);
+
+ if (ret < 0)
+ dev_err(&stfm1000->client->dev, "%s: failed", __func__);
+
+ return ret;
+}
+
+int stfm1000_raw_read(struct stfm1000 *stfm1000, int reg, u32 *value)
+{
+ int ret;
+
+ mutex_lock(&stfm1000->xfer_lock);
+ ret = read_reg_internal(stfm1000, reg, value);
+ mutex_unlock(&stfm1000->xfer_lock);
+
+ if (ret < 0)
+ dev_err(&stfm1000->client->dev, "%s: failed", __func__);
+
+ return ret;
+}
+
+static inline void stfm1000_set_shadow_reg(struct stfm1000 *stfm1000,
+ int reg, u32 val)
+{
+ stfm1000->shadow_regs[reg / 4] = val;
+}
+
+static inline u32 stfm1000_get_shadow_reg(struct stfm1000 *stfm1000, int reg)
+{
+ return stfm1000->shadow_regs[reg / 4];
+}
+
+int stfm1000_write(struct stfm1000 *stfm1000, int reg, u32 value)
+{
+ int ret;
+
+ if (!stfm1000_reg_is_writeable(stfm1000, reg)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&stfm1000->xfer_lock);
+
+ /* same value as last one written? */
+ if (stfm1000_reg_is_dirty(stfm1000, reg) &&
+ stfm1000_get_shadow_reg(stfm1000, reg) == value) {
+ ret = 0;
+
+ stfm1000_i2c_debug(stfm1000, "%s - HIT "
+ "(%s - 0x%02x, 0x%08x)\n", __func__,
+ reg_names[reg / 4], reg, value);
+
+ goto out_unlock;
+ }
+
+ /* actually write the register */
+ ret = write_reg_internal(stfm1000, reg, value);
+ if (ret < 0)
+ goto out_unlock;
+
+ /* update shadow register & mark it as dirty */
+ /* only if register is not read always */
+ if (!stfm1000_reg_is_ra(stfm1000, reg)) {
+ stfm1000_set_shadow_reg(stfm1000, reg, value);
+ stfm1000_reg_set_dirty(stfm1000, reg);
+ }
+
+out_unlock:
+ mutex_unlock(&stfm1000->xfer_lock);
+
+out:
+ if (ret < 0)
+ dev_err(&stfm1000->client->dev, "%s: failed", __func__);
+
+ if (verify_writes) {
+ u32 value2 = ~0;
+
+ stfm1000_raw_read(stfm1000, reg, &value2);
+
+ stfm1000_i2c_debug(stfm1000, "%s - VER "
+ "(%s - 0x%02x, W=0x%08x V=0x%08x) %s\n", __func__,
+ reg_names[reg / 4], reg, value, value2,
+ value == value2 ? "OK" : "** differs **");
+ }
+
+ return ret;
+}
+
+int stfm1000_read(struct stfm1000 *stfm1000, int reg, u32 *value)
+{
+ int ret = 0;
+
+ if (!stfm1000_reg_is_readable(stfm1000, reg)) {
+ ret = -EINVAL;
+ printk(KERN_INFO "%s: !readable(%d)\n", __func__, reg);
+ goto out;
+ }
+
+ mutex_lock(&stfm1000->xfer_lock);
+
+ /* if the register can be written & is dirty, use the shadow */
+ if (stfm1000_reg_is_writeable(stfm1000, reg) &&
+ stfm1000_reg_is_dirty(stfm1000, reg)) {
+
+ *value = stfm1000_get_shadow_reg(stfm1000, reg);
+ ret = 0;
+
+ stfm1000_i2c_debug(stfm1000, "%s - HIT "
+ "(%s - 0x%02x, 0x%08x)\n", __func__,
+ reg_names[reg / 4], reg, *value);
+
+ goto out_unlock;
+ }
+
+ /* register must be read */
+ ret = read_reg_internal(stfm1000, reg, value);
+ if (ret < 0)
+ goto out;
+
+ /* if the register is writeable, update shadow */
+ if (stfm1000_reg_is_writeable(stfm1000, reg)) {
+ stfm1000_set_shadow_reg(stfm1000, reg, *value);
+ stfm1000_reg_set_dirty(stfm1000, reg);
+ }
+
+out_unlock:
+ mutex_unlock(&stfm1000->xfer_lock);
+
+out:
+ if (ret < 0)
+ dev_err(&stfm1000->client->dev, "%s: failed", __func__);
+
+ return ret;
+}
+
+int stfm1000_write_masked(struct stfm1000 *stfm1000, int reg, u32 value,
+ u32 mask)
+{
+ int ret = 0;
+ u32 old_value;
+
+ if (!stfm1000_reg_is_writeable(stfm1000, reg)) {
+ ret = -EINVAL;
+ printk(KERN_ERR "%s: !writeable(%d)\n", __func__, reg);
+ goto out;
+ }
+
+ mutex_lock(&stfm1000->xfer_lock);
+
+ /* if the register wasn't written before, read it */
+ if (!stfm1000_reg_is_dirty(stfm1000, reg)) {
+ ret = read_reg_internal(stfm1000, reg, &old_value);
+ if (ret != 0)
+ goto out_unlock;
+ } else /* register was written, use the last value */
+ old_value = stfm1000_get_shadow_reg(stfm1000, reg);
+
+ /* perform masking */
+ value = (old_value & ~mask) | (value & mask);
+
+ /* if we write the same value, don't bother */
+ if (stfm1000_reg_is_dirty(stfm1000, reg) && value == old_value) {
+ ret = 0;
+
+ stfm1000_i2c_debug(stfm1000, "%s - HIT "
+ "(%s - 0x%02x, 0x%08x)\n", __func__,
+ reg_names[reg / 4], reg, value);
+
+ goto out_unlock;
+ }
+
+ /* actually write the register to the chip */
+ ret = write_reg_internal(stfm1000, reg, value);
+ if (ret < 0)
+ goto out_unlock;
+
+ /* if no error, update the shadow register and mark it as dirty */
+ stfm1000_set_shadow_reg(stfm1000, reg, value);
+ stfm1000_reg_set_dirty(stfm1000, reg);
+
+out_unlock:
+ mutex_unlock(&stfm1000->xfer_lock);
+
+out:
+ if (ret < 0)
+ dev_err(&stfm1000->client->dev, "%s: failed", __func__);
+
+ if (verify_writes) {
+ u32 value2 = ~0;
+
+ stfm1000_raw_read(stfm1000, reg, &value2);
+
+ stfm1000_i2c_debug(stfm1000, "%s - VER "
+ "(%s - 0x%02x, W=0x%08x V=0x%08x) %s\n", __func__,
+ reg_names[reg / 4], reg, value, value2,
+ value == value2 ? "OK" : "** differs **");
+ }
+
+ return ret;
+}
+
+int stfm1000_set_bits(struct stfm1000 *stfm1000, int reg, u32 value)
+{
+ return stfm1000_write_masked(stfm1000, reg, value, value);
+}
+
+int stfm1000_clear_bits(struct stfm1000 *stfm1000, int reg, u32 value)
+{
+ return stfm1000_write_masked(stfm1000, reg, ~value, value);
+}
+
+int stfm1000_write_regs(struct stfm1000 *stfm1000,
+ const struct stfm1000_reg *reg)
+{
+ int ret;
+
+ for (; reg && reg->regno != STFM1000_REG_END; reg++) {
+
+ if (reg->regno == STFM1000_REG_DELAY) {
+ msleep(reg->value);
+ continue;
+ }
+
+ if (reg->regno & STFM1000_REG_SET_BITS_MASK)
+ ret = stfm1000_set_bits(stfm1000, reg->regno & 0xff,
+ reg->value);
+ else if (reg->regno & STFM1000_REG_CLEAR_BITS_MASK)
+ ret = stfm1000_clear_bits(stfm1000, reg->regno & 0xff,
+ reg->value);
+ else
+ ret = stfm1000_write(stfm1000, reg->regno, reg->value);
+
+ if (ret != 0) {
+ printk(KERN_ERR "%s: failed to write reg 0x%x "
+ "with 0x%08x\n",
+ __func__, reg->regno, reg->value);
+ return ret;
+ }
+ }
+ return 0;
+}
+