diff options
Diffstat (limited to 'drivers/media/radio/stfm1000/stfm1000-core.c')
-rw-r--r-- | drivers/media/radio/stfm1000/stfm1000-core.c | 2459 |
1 files changed, 2459 insertions, 0 deletions
diff --git a/drivers/media/radio/stfm1000/stfm1000-core.c b/drivers/media/radio/stfm1000/stfm1000-core.c new file mode 100644 index 000000000000..5086100bb480 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-core.c @@ -0,0 +1,2459 @@ +/* + * 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/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/math64.h> + +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/version.h> /* for KERNEL_VERSION MACRO */ +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/device.h> +#include <linux/freezer.h> +#include <linux/kthread.h> +#include <mach/regs-dri.h> +#include <mach/regs-apbx.h> +#include <mach/regs-clkctrl.h> + +#include "stfm1000.h" + +static DEFINE_MUTEX(devlist_lock); +static unsigned int stfm1000_devcount; + +LIST_HEAD(stfm1000_devlist); +EXPORT_SYMBOL(stfm1000_devlist); + +/* alsa interface */ +struct stfm1000_alsa_ops *stfm1000_alsa_ops; +EXPORT_SYMBOL(stfm1000_alsa_ops); + +/* region, 0=US, 1=europe */ +static int georegion = 1; /* default is europe */ +static int rds_enable = 1; /* default is enabled */ + +static int sw_tune(struct stfm1000 *stfm1000, u32 freq); + +static const const char *stfm1000_get_rev_txt(u32 id) +{ + switch (id) { + case 0x01: return "TA1"; + case 0x02: return "TA2"; + case 0x11: return "TB1"; + case 0x12: return "TB2"; + } + return NULL; +} + +static const struct stfm1000_reg stfm1000_tb2_powerup[] = { + STFM1000_REG(REF, 0x00200000), + STFM1000_DELAY(20), + STFM1000_REG(DATAPATH, 0x00010210), + STFM1000_REG(TUNE1, 0x0004CF01), + STFM1000_REG(SDNOMINAL, 0x1C5EBCF0), + STFM1000_REG(PILOTTRACKING, 0x000001B6), + STFM1000_REG(INITIALIZATION1, 0x9fb80008), + STFM1000_REG(INITIALIZATION2, 0x8516e444 | STFM1000_DEEMPH_50_75B), + STFM1000_REG(INITIALIZATION3, 0x1402190b), + STFM1000_REG(INITIALIZATION4, 0x525bf052), + STFM1000_REG(INITIALIZATION5, 0x1000d106), + STFM1000_REG(INITIALIZATION6, 0x000062cb), + STFM1000_REG(AGC_CONTROL1, 0x1BCB2202), + STFM1000_REG(AGC_CONTROL2, 0x000020F0), + STFM1000_REG(CLK1, 0x10000000), + STFM1000_REG(CLK1, 0x20000000), + STFM1000_REG(CLK1, 0x00000000), + STFM1000_REG(CLK2, 0x7f000000), + STFM1000_REG(REF, 0x00B8222D), + STFM1000_REG(CLK1, 0x30000000), + STFM1000_REG(CLK1, 0x30002000), + STFM1000_REG(CLK1, 0x10002000), + STFM1000_REG(LNA, 0x0D080009), + STFM1000_DELAY(10), + STFM1000_REG(MIXFILT, 0x00008000), + STFM1000_REG(MIXFILT, 0x00000000), + STFM1000_REG(MIXFILT, 0x00007205), + STFM1000_REG(ADC, 0x001B3282), + STFM1000_REG(ATTENTION, 0x0000003F), + STFM1000_END, +}; + +static const struct stfm1000_reg stfm1000_ta2_powerup[] = { + STFM1000_REG(REF, 0x00200000), + STFM1000_DELAY(20), + STFM1000_REG(DATAPATH, 0x00010210), + STFM1000_REG(TUNE1, 0x00044F01), + STFM1000_REG(SDNOMINAL, 0x1C5EBCF0), + STFM1000_REG(PILOTTRACKING, 0x000001B6), + STFM1000_REG(INITIALIZATION1, 0x9fb80008), + STFM1000_REG(INITIALIZATION2, 0x8506e444), + STFM1000_REG(INITIALIZATION3, 0x1402190b), + STFM1000_REG(INITIALIZATION4, 0x525bf052), + STFM1000_REG(INITIALIZATION5, 0x7000d106), + STFM1000_REG(INITIALIZATION6, 0x0000c2cb), + STFM1000_REG(AGC_CONTROL1, 0x002c8402), + STFM1000_REG(AGC_CONTROL2, 0x00140050), + STFM1000_REG(CLK1, 0x10000000), + STFM1000_REG(CLK1, 0x20000000), + STFM1000_REG(CLK1, 0x00000000), + STFM1000_REG(CLK2, 0x7f000000), + STFM1000_REG(REF, 0x0030222D), + STFM1000_REG(CLK1, 0x30000000), + STFM1000_REG(CLK1, 0x30002000), + STFM1000_REG(CLK1, 0x10002000), + STFM1000_REG(LNA, 0x05080009), + STFM1000_REG(MIXFILT, 0x00008000), + STFM1000_REG(MIXFILT, 0x00000000), + STFM1000_REG(MIXFILT, 0x00007200), + STFM1000_REG(ADC, 0x00033000), + STFM1000_REG(ATTENTION, 0x0000003F), + STFM1000_END, +}; + +static const struct stfm1000_reg stfm1000_powerdown[] = { + STFM1000_REG(DATAPATH, 0x00010210), + STFM1000_REG(REF, 0), + STFM1000_REG(LNA, 0), + STFM1000_REG(MIXFILT, 0), + STFM1000_REG(CLK1, 0x20000000), + STFM1000_REG(CLK1, 0), + STFM1000_REG(CLK2, 0), + STFM1000_REG(ADC, 0), + STFM1000_REG(TUNE1, 0), + STFM1000_REG(SDNOMINAL, 0), + STFM1000_REG(PILOTTRACKING, 0), + STFM1000_REG(INITIALIZATION1, 0), + STFM1000_REG(INITIALIZATION2, 0), + STFM1000_REG(INITIALIZATION3, 0), + STFM1000_REG(INITIALIZATION4, 0), + STFM1000_REG(INITIALIZATION5, 0), + STFM1000_REG(INITIALIZATION6, 0x00007E00), + STFM1000_REG(AGC_CONTROL1, 0), + STFM1000_REG(AGC_CONTROL2, 0), + STFM1000_REG(DATAPATH, 0x00000200), +}; + +struct stfm1000_tuner_pmi { + u32 min; + u32 max; + u32 freq; + u32 pll_xtal; /* 1 = pll, 0 = xtal */ +}; + +#define PLL 1 +#define XTAL 0 + +static const struct stfm1000_tuner_pmi stfm1000_pmi_lookup[] = { + { .min = 76100, .max = 76500, .freq = 19200, .pll_xtal = PLL }, + { .min = 79700, .max = 79900, .freq = 19200, .pll_xtal = PLL }, + { .min = 80800, .max = 81200, .freq = 19200, .pll_xtal = PLL }, + { .min = 82100, .max = 82600, .freq = 19200, .pll_xtal = PLL }, + { .min = 86800, .max = 87200, .freq = 19200, .pll_xtal = PLL }, + { .min = 88100, .max = 88600, .freq = 19200, .pll_xtal = PLL }, + { .min = 89800, .max = 90500, .freq = 19200, .pll_xtal = PLL }, + { .min = 91400, .max = 91900, .freq = 19200, .pll_xtal = PLL }, + { .min = 92800, .max = 93300, .freq = 19200, .pll_xtal = PLL }, + { .min = 97400, .max = 97900, .freq = 19200, .pll_xtal = PLL }, + { .min = 98800, .max = 99200, .freq = 19200, .pll_xtal = PLL }, + { .min = 100200, .max = 100400, .freq = 19200, .pll_xtal = PLL }, + { .min = 103500, .max = 103900, .freq = 19200, .pll_xtal = PLL }, + { .min = 104800, .max = 105200, .freq = 19200, .pll_xtal = PLL }, + { .min = 106100, .max = 106500, .freq = 19200, .pll_xtal = PLL }, + + { .min = 76600, .max = 77000, .freq = 20000, .pll_xtal = PLL }, + { .min = 77800, .max = 78300, .freq = 20000, .pll_xtal = PLL }, + { .min = 79200, .max = 79600, .freq = 20000, .pll_xtal = PLL }, + { .min = 80600, .max = 80700, .freq = 20000, .pll_xtal = PLL }, + { .min = 83900, .max = 84400, .freq = 20000, .pll_xtal = PLL }, + { .min = 85300, .max = 85800, .freq = 20000, .pll_xtal = PLL }, + { .min = 94200, .max = 94700, .freq = 20000, .pll_xtal = PLL }, + { .min = 95600, .max = 96100, .freq = 20000, .pll_xtal = PLL }, + { .min = 100500, .max = 100800, .freq = 20000, .pll_xtal = PLL }, + { .min = 101800, .max = 102200, .freq = 20000, .pll_xtal = PLL }, + { .min = 103100, .max = 103400, .freq = 20000, .pll_xtal = PLL }, + { .min = 106600, .max = 106900, .freq = 20000, .pll_xtal = PLL }, + { .min = 107800, .max = 108000, .freq = 20000, .pll_xtal = PLL }, + + { .min = 0, .max = 0, .freq = 24000, .pll_xtal = XTAL } +}; + +int stfm1000_power_up(struct stfm1000 *stfm1000) +{ + struct stfm1000_reg *reg, *pwrup_reg; + const struct stfm1000_reg *orig_reg, *treg; + int ret, size; + + mutex_lock(&stfm1000->state_lock); + + /* Enable DRI clock for 24Mhz. */ + HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); + + orig_reg = stfm1000->revid == STFM1000_CHIP_REV_TA2 ? + stfm1000_ta2_powerup : stfm1000_tb2_powerup; + + /* find size of the set */ + for (treg = orig_reg; treg->regno != STFM1000_REG_END; treg++) + ; + size = (treg + 1 - orig_reg) * sizeof(*treg); + + /* allocate copy */ + pwrup_reg = kmalloc(size, GFP_KERNEL); + if (pwrup_reg == NULL) { + printk(KERN_ERR "%s: out of memory\n", __func__); + ret = -ENOMEM; + goto out; + } + + /* copy it */ + memcpy(pwrup_reg, orig_reg, size); + + /* fixup region of INITILIZATION2 */ + for (reg = pwrup_reg; reg->regno != STFM1000_REG_END; reg++) { + + /* we only care for INITIALIZATION2 register */ + if (reg->regno != STFM1000_INITIALIZATION2) + continue; + + /* geographic region select */ + if (stfm1000->georegion == 0) /* USA */ + reg->value &= ~STFM1000_DEEMPH_50_75B; + else /* Europe */ + reg->value |= STFM1000_DEEMPH_50_75B; + + /* RDS enabled */ + if (stfm1000->revid == STFM1000_CHIP_REV_TB2) { + if (stfm1000->rds_enable) + reg->value |= STFM1000_RDS_ENABLE; + else + reg->value &= ~STFM1000_RDS_ENABLE; + } + } + + ret = stfm1000_write_regs(stfm1000, pwrup_reg); + + kfree(pwrup_reg); +out: + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +int stfm1000_power_down(struct stfm1000 *stfm1000) +{ + int ret; + + mutex_lock(&stfm1000->state_lock); + + /* Disable DRI clock for 24Mhz. */ + HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); + + ret = stfm1000_write_regs(stfm1000, stfm1000_powerdown); + + /* Disable DRI clock for 24Mhz. */ + /* XXX bug warning, disabling the DRI clock is bad news */ + /* doing so causes noise to be received from the DRI */ + /* interface. Leave it on for now */ + /* HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); */ + + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +int stfm1000_dcdc_update(struct stfm1000 *stfm1000, u32 freq) +{ + const struct stfm1000_tuner_pmi *pmi; + int i; + + /* search for DCDC frequency */ + pmi = stfm1000_pmi_lookup; + for (i = 0; i < ARRAY_SIZE(stfm1000_pmi_lookup); i++, pmi++) { + if (freq >= pmi->min && freq <= pmi->max) + break; + } + if (i >= ARRAY_SIZE(stfm1000_pmi_lookup)) + return -1; + + /* adjust DCDC frequency so that it is out of Tuner PLL range */ + /* XXX there is no adjustment API (os_pmi_SetDcdcFreq)*/ + return 0; +} + +static void Mute_Audio(struct stfm1000 *stfm1000) +{ + stfm1000->mute = 1; +} + +static void Unmute_Audio(struct stfm1000 *stfm1000) +{ + stfm1000->mute = 0; +} + +static const struct stfm1000_reg sd_dp_on_regs[] = { + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DP_EN), + STFM1000_DELAY(3), + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_REG_CLRBITS(AGC_CONTROL1, STFM1000_B2_BYPASS_AGC_CTL), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_END, +}; + +static int SD_DP_On(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_write_regs(stfm1000, sd_dp_on_regs); + if (ret != 0) + return ret; + + return 0; +} + +static const struct stfm1000_reg sd_dp_off_regs[] = { + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DP_EN), + STFM1000_REG_SETBITS(AGC_CONTROL1, STFM1000_B2_BYPASS_AGC_CTL), + STFM1000_REG_CLRBITS(PILOTTRACKING, STFM1000_B2_PILOTTRACKING_EN), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_END, +}; + +static int SD_DP_Off(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_write_regs(stfm1000, sd_dp_off_regs); + if (ret != 0) + return ret; + + return 0; +} + +static int DRI_Start_Stream(struct stfm1000 *stfm1000) +{ + dma_addr_t dma_buffer_phys; + int i, next; + u32 cmd; + + /* we must not be gated */ + BUG_ON(HW_CLKCTRL_XTAL_RD() & BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); + + /* hw_dri_SetReset */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_SFTRST | BM_DRI_CTRL_CLKGATE); + HW_DRI_CTRL_SET(BM_DRI_CTRL_SFTRST); + while ((HW_DRI_CTRL_RD() & BM_DRI_CTRL_CLKGATE) == 0) + cpu_relax(); + HW_DRI_CTRL_CLR(BM_DRI_CTRL_SFTRST | BM_DRI_CTRL_CLKGATE); + + /* DRI enable/config */ + HW_DRI_TIMING_WR(BF_DRI_TIMING_GAP_DETECTION_INTERVAL(0x10) | + BF_DRI_TIMING_PILOT_REP_RATE(0x08)); + + /* XXX SDK bug */ + /* While the SDK enables the gate here, everytime the stream */ + /* is started, doing so, causes the DRI to input audio noise */ + /* at any subsequent starts */ + /* Enable DRI clock for 24Mhz. */ + /* HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); */ + + stmp3xxx_arch_dma_reset_channel(stfm1000->dma_ch); + + dma_buffer_phys = stfm1000->dri_phys; + + for (i = 0; i < stfm1000->blocks; i++) { + next = (i + 1) % stfm1000->blocks; + + /* link */ + stfm1000->dma[i].command->next = stfm1000->dma[next].handle; + stfm1000->dma[i].next_descr = &stfm1000->dma[next]; + + /* receive DRI is 8 bytes per 4 samples */ + cmd = BF_APBX_CHn_CMD_XFER_COUNT(stfm1000->blksize * 2) | + BM_APBX_CHn_CMD_IRQONCMPLT | + BM_APBX_CHn_CMD_CHAIN | + BF_APBX_CHn_CMD_COMMAND( + BV_APBX_CHn_CMD_COMMAND__DMA_WRITE); + + stfm1000->dma[i].command->cmd = cmd; + stfm1000->dma[i].command->buf_ptr = dma_buffer_phys; + stfm1000->dma[i].command->pio_words[0] = + BM_DRI_CTRL_OVERFLOW_IRQ_EN | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ_EN | + BM_DRI_CTRL_ATTENTION_IRQ_EN | + /* BM_DRI_CTRL_STOP_ON_OFLOW_ERROR | */ + /* BM_DRI_CTRL_STOP_ON_PILOT_ERROR | */ + BM_DRI_CTRL_ENABLE_INPUTS; + + dma_buffer_phys += stfm1000->blksize * 2; + + } + + /* Enable completion interrupt */ + stmp3xxx_dma_clear_interrupt(stfm1000->dma_ch); + stmp3xxx_dma_enable_interrupt(stfm1000->dma_ch); + + /* clear DRI interrupts pending */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_OVERFLOW_IRQ | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ | + BM_DRI_CTRL_ATTENTION_IRQ); + + /* Stop DRI on error */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_STOP_ON_OFLOW_ERROR | + BM_DRI_CTRL_STOP_ON_PILOT_ERROR); + + /* Reacquire data stream */ + HW_DRI_CTRL_SET(BM_DRI_CTRL_REACQUIRE_PHASE | + BM_DRI_CTRL_OVERFLOW_IRQ_EN | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ_EN | + BM_DRI_CTRL_ATTENTION_IRQ_EN | + BM_DRI_CTRL_ENABLE_INPUTS); + + stmp3xxx_dma_go(stfm1000->dma_ch, stfm1000->dma, 1); + + /* Turn on DRI hardware (don't forget to leave RUN bit ON) */ + HW_DRI_CTRL_SET(BM_DRI_CTRL_RUN); + + return 0; +} + +static int DRI_Stop_Stream(struct stfm1000 *stfm1000) +{ + int desc; + + /* disable interrupts */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_OVERFLOW_IRQ_EN | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ_EN | + BM_DRI_CTRL_ATTENTION_IRQ_EN); + + /* Freeze DMA channel for a moment */ + stmp3xxx_dma_freeze(stfm1000->dma_ch); + + /* all descriptors, set sema bit */ + for (desc = 0; desc < stfm1000->blocks; desc++) + stfm1000->dma[desc].command->cmd |= BM_APBX_CHn_CMD_SEMAPHORE; + + /* Let the current DMA transaction finish */ + stmp3xxx_dma_unfreeze(stfm1000->dma_ch); + msleep(5); + + /* dma shutdown */ + stmp3xxx_arch_dma_reset_channel(stfm1000->dma_ch); + + /* Turn OFF data lines and stop controller */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_ENABLE_INPUTS | BM_DRI_CTRL_RUN); + + /* hw_dri_SetReset */ + HW_DRI_CTRL_SET(BM_DRI_CTRL_SFTRST | BM_DRI_CTRL_CLKGATE); + + /* XXX SDK bug */ + /* While the SDK enables the gate here, everytime the stream */ + /* is started, doing so, causes the DRI to input audio noise */ + /* at any subsequent starts */ + /* Enable DRI clock for 24Mhz. */ + /* Disable DRI clock for 24Mhz. */ + /* HW_CLKCTRL_XTAL_SET(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); */ + + return 0; +} + +static int DRI_On(struct stfm1000 *stfm1000) +{ + int ret; + + if (stfm1000->active) + DRI_Start_Stream(stfm1000); + + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_SAI_EN); + return ret; +} + +static int DRI_Off(struct stfm1000 *stfm1000) +{ + int ret; + + if (stfm1000->active) + DRI_Stop_Stream(stfm1000); + + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_SAI_EN); + + return 0; +} + +static int SD_Set_Channel_Filter(struct stfm1000 *stfm1000) +{ + int bypass_setting; + int sig_qual; + u32 tmp; + int ret; + + /* + * set channel filter + * + * B2_NEAR_CHAN_MIX_REG_MASK values from T-Spec + * 000 : 0 kHz mix. + * 001 : +100 kHz mix. + * 010 : +200 kHz mix. + * 011 : +300 kHz mix. + * 100 : -400 kHz mix. + * 101 : -300 kHz mix. + * 110 : -200 kHz mix. + * 111 : -100 kHz mix. + */ + + /* get near channel amplitude */ + ret = stfm1000_write_masked(stfm1000, STFM1000_INITIALIZATION3, + STFM1000_B2_NEAR_CHAN_MIX(0x01), + STFM1000_B2_NEAR_CHAN_MIX_MASK); + if (ret != 0) + return ret; + + msleep(10); /* wait for the signal quality to settle */ + + ret = stfm1000_read(stfm1000, STFM1000_SIGNALQUALITY, &tmp); + if (ret != 0) + return ret; + + sig_qual = (tmp & STFM1000_NEAR_CHAN_AMPLITUDE_MASK) >> + STFM1000_NEAR_CHAN_AMPLITUDE_SHIFT; + + bypass_setting = 0; + + /* check near channel amplitude vs threshold */ + if (sig_qual < stfm1000->adj_chan_th) { + /* get near channel amplitude again */ + ret = stfm1000_write_masked(stfm1000, STFM1000_INITIALIZATION3, + STFM1000_B2_NEAR_CHAN_MIX(0x05), + STFM1000_B2_NEAR_CHAN_MIX_MASK); + if (ret != 0) + return ret; + + msleep(10); /* wait for the signal quality to settle */ + + ret = stfm1000_read(stfm1000, STFM1000_SIGNALQUALITY, &tmp); + if (ret != 0) + return ret; + + sig_qual = (tmp & STFM1000_NEAR_CHAN_AMPLITUDE_MASK) >> + STFM1000_NEAR_CHAN_AMPLITUDE_SHIFT; + + if (sig_qual < stfm1000->adj_chan_th) + bypass_setting = 2; + } + + /* set filter settings */ + ret = stfm1000_write_masked(stfm1000, STFM1000_INITIALIZATION1, + STFM1000_B2_BYPASS_FILT(bypass_setting), + STFM1000_B2_BYPASS_FILT_MASK); + if (ret != 0) + return ret; + + return 0; +} + +static int SD_Look_For_Pilot_TA2(struct stfm1000 *stfm1000) +{ + int i; + u32 pilot; + int ret; + + /* assume pilot */ + stfm1000->pilot_present = 1; + + for (i = 0; i < 3; i++) { + + ret = stfm1000_read(stfm1000, STFM1000_PILOTCORRECTION, + &pilot); + if (ret != 0) + return ret; + + pilot &= STFM1000_PILOTEST_TA2_MASK; + pilot >>= STFM1000_PILOTEST_TA2_SHIFT; + + /* out of range? */ + if (pilot < 0xe2 || pilot >= 0xb5) { + stfm1000->pilot_present = 0; + break; + } + } + + return 0; +} + + +static int SD_Look_For_Pilot_TB2(struct stfm1000 *stfm1000) +{ + int i; + u32 pilot; + int ret; + + /* assume pilot */ + stfm1000->pilot_present = 1; + + for (i = 0; i < 3; i++) { + + ret = stfm1000_read(stfm1000, STFM1000_PILOTCORRECTION, + &pilot); + if (ret != 0) + return ret; + + pilot &= STFM1000_PILOTEST_TB2_MASK; + pilot >>= STFM1000_PILOTEST_TB2_SHIFT; + + /* out of range? */ + if (pilot < 0x1e || pilot >= 0x7f) { + stfm1000->pilot_present = 0; + break; + } + } + + return 0; +} + +static int SD_Look_For_Pilot(struct stfm1000 *stfm1000) +{ + int ret; + + if (stfm1000->revid == STFM1000_CHIP_REV_TA2) + ret = SD_Look_For_Pilot_TA2(stfm1000); + else + ret = SD_Look_For_Pilot_TB2(stfm1000); + + if (ret != 0) + return ret; + + if (!stfm1000->pilot_present) { + ret = stfm1000_clear_bits(stfm1000, STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + } + + return 0; +} + +static int SD_Gear_Shift_Pilot_Tracking(struct stfm1000 *stfm1000) +{ + static const struct { + int delay; + u32 value; + } track_table[] = { + { .delay = 10, .value = 0x81b6 }, + { .delay = 6, .value = 0x82a5 }, + { .delay = 6, .value = 0x8395 }, + { .delay = 8, .value = 0x8474 }, + { .delay = 20, .value = 0x8535 }, + { .delay = 50, .value = 0x8632 }, + { .delay = 0, .value = 0x8810 }, + }; + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(track_table); i++) { + ret = stfm1000_write(stfm1000, STFM1000_PILOTTRACKING, + track_table[i].value); + if (ret != 0) + return ret; + + if (i < ARRAY_SIZE(track_table) - 1) /* last one no delay */ + msleep(track_table[i].delay); + } + + return 0; +} + +static int SD_Optimize_Channel(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + ret = stfm1000_write(stfm1000, STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN | + STFM1000_B2_PILOTLPF_TIMECONSTANT(0x01) | + STFM1000_B2_PFDSCALE(0x0B) | + STFM1000_B2_PFDFILTER_SPEEDUP(0x06)); /* 0x000081B6 */ + if (ret != 0) + return ret; + + ret = SD_Set_Channel_Filter(stfm1000); + if (ret != 0) + return ret; + + ret = SD_Look_For_Pilot(stfm1000); + if (ret != 0) + return ret; + + if (stfm1000->pilot_present) { + ret = SD_Gear_Shift_Pilot_Tracking(stfm1000); + if (ret != 0) + return ret; + } + + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + return 0; +} + +static int Monitor_STFM_Quality(struct stfm1000 *stfm1000) +{ + u32 tmp, rssi_dc_est, tone_data; + u32 lna_rms, bias, agc_out, lna_th, lna, ref; + u16 rssi_mantissa, rssi_exponent, rssi_decoded; + u16 prssi; + s16 mpx_dc; + int rssi_log; + int bypass_filter; + int ret; + + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + /* Get Rssi register readings from STFM1000 */ + stfm1000_read(stfm1000, STFM1000_RSSI_TONE, &tmp); + rssi_dc_est = tmp & 0xffff; + tone_data = (tmp >> 16) & 0x0fff; + + rssi_mantissa = (rssi_dc_est & 0xffe0) >> 5; /* 11Msb */ + rssi_exponent = rssi_dc_est & 0x001f; /* 5 lsb */ + rssi_decoded = (u32)rssi_mantissa << rssi_exponent; + + /* Convert Rsst to 10log(Rssi) */ + for (prssi = 20; prssi > 0; prssi--) + if (rssi_decoded >= (1 << prssi)) + break; + + rssi_log = (3 * rssi_decoded >> prssi) + (3 * prssi - 3); + /* clamp to positive */ + if (rssi_log < 0) + rssi_log = 0; + /* Compensate for errors in truncation/approximation by adding 1 */ + rssi_log++; + + stfm1000->rssi_dc_est_log = rssi_log; + stfm1000->signal_strength = stfm1000->rssi_dc_est_log; + + /* determine absolute value */ + if (tmp & 0x0800) + mpx_dc = ((tmp >> 16) & 0x0fff) | 0xf000; + else + mpx_dc = (tmp >> 16) & 0x0fff; + stfm1000->mpx_dc = mpx_dc; + mpx_dc = mpx_dc < 0 ? -mpx_dc : mpx_dc; + + if (stfm1000->tuning_grid_50KHz) + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th; + else + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th && + mpx_dc > stfm1000->tune_mpx_dc_th; + + /* weak signal? */ + if (stfm1000->rssi_dc_est_log < + (stfm1000->filter_parms.pCoefLmrGaTh - 20)) { + + if (stfm1000->pilot_present) + bypass_filter = 1; /* Filter settings #2 */ + else + bypass_filter = 0; + + /* configure filter for narrow band */ + ret = stfm1000_write_masked(stfm1000, STFM1000_AGC_CONTROL1, + STFM1000_B2_BYPASS_FILT(bypass_filter), + STFM1000_B2_BYPASS_FILT_MASK); + if (ret != 0) + return ret; + + /* Turn off pilot tracking */ + ret = stfm1000_clear_bits(stfm1000, STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* enable "forced mono" in black box */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + + /* Set weak signal flag */ + stfm1000->weak_signal = 1; + + if (stfm1000->revid == STFM1000_CHIP_REV_TA2) { + + /* read AGC_STAT register */ + ret = stfm1000_read(stfm1000, STFM1000_AGC_STAT, &tmp); + if (ret != 0) + return ret; + + lna_rms = (tmp & STFM1000_LNA_RMS_MASK) >> + STFM1000_LNA_RMS_SHIFT; + + /* Check the energy level from LNA Power Meter A/D */ + if (lna_rms == 0) + bias = STFM1000_IBIAS2_DN | STFM1000_IBIAS1_UP; + else + bias = STFM1000_IBIAS2_UP | STFM1000_IBIAS1_DN; + + if (lna_rms == 0 || lna_rms > 2) { + ret = stfm1000_write_masked(stfm1000, + STFM1000_LNA, bias, + STFM1000_IBIAS2_UP | + STFM1000_IBIAS2_DN | + STFM1000_IBIAS1_UP | + STFM1000_IBIAS1_DN); + if (ret != 0) + return ret; + } + + } else { + + /* Set LNA bias */ + + /* read AGC_STAT register */ + ret = stfm1000_read(stfm1000, STFM1000_AGC_STAT, &tmp); + if (ret != 0) + return ret; + + agc_out = (tmp & STFM1000_AGCOUT_STAT_MASK) >> + STFM1000_AGCOUT_STAT_SHIFT; + + /* read LNA register (this is a cached register) */ + ret = stfm1000_read(stfm1000, STFM1000_LNA, &lna); + if (ret != 0) + return ret; + + /* read REF register (this is a cached register) */ + ret = stfm1000_read(stfm1000, STFM1000_REF, &ref); + if (ret != 0) + return ret; + +/* work around the 80 line width problem */ +#undef LNADEF +#define LNADEF STFM1000_LNA_AMP1_IMPROVE_DISTORTION + if (agc_out == 31) { + if (rssi_log <= 16) { + if (lna & STFM1000_IBIAS1_DN) + lna &= ~STFM1000_IBIAS1_DN; + else { + lna |= STFM1000_IBIAS1_UP; + ref &= ~LNADEF; + } + } + if (rssi_log >= 26) { + if (lna & STFM1000_IBIAS1_UP) { + lna &= ~STFM1000_IBIAS1_UP; + ref |= LNADEF; + } else + lna |= STFM1000_IBIAS1_DN; + } + } else { + lna &= ~STFM1000_IBIAS1_UP; + lna |= STFM1000_IBIAS1_DN; + ref |= LNADEF; + } +#undef LNADEF + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + lna, STFM1000_IBIAS1_UP | STFM1000_IBIAS1_DN); + if (ret != 0) + return ret; + + ret = stfm1000_write_masked(stfm1000, STFM1000_REF, + ref, STFM1000_LNA_AMP1_IMPROVE_DISTORTION); + if (ret != 0) + return ret; + } + + } else if (stfm1000->rssi_dc_est_log > + (stfm1000->filter_parms.pCoefLmrGaTh - 17)) { + + bias = STFM1000_IBIAS2_UP | STFM1000_IBIAS1_DN; + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + bias, STFM1000_IBIAS2_UP | STFM1000_IBIAS2_DN | + STFM1000_IBIAS1_UP | STFM1000_IBIAS1_DN); + if (ret != 0) + return ret; + + ret = SD_Set_Channel_Filter(stfm1000); + if (ret != 0) + return ret; + + ret = SD_Look_For_Pilot(stfm1000); + if (ret != 0) + return ret; + + if (stfm1000->pilot_present) { + if (stfm1000->prev_pilot_present || + stfm1000->weak_signal) { + + /* gear shift pilot tracking */ + ret = SD_Gear_Shift_Pilot_Tracking( + stfm1000); + if (ret != 0) + return ret; + + /* set force mono parameters for the + * filter */ + stfm1000->filter_parms. + pCoefForcedMono = stfm1000-> + force_mono; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod. + pCoefForcedMono = stfm1000-> + filter_parms. + pCoefForcedMono; + } + } else { + ret = stfm1000_clear_bits(stfm1000, + STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + } + + /* Reset weak signal flag */ + stfm1000->weak_signal = 0; + stfm1000->prev_pilot_present = stfm1000->pilot_present; + + } else { + + ret = SD_Look_For_Pilot(stfm1000); + if (ret != 0) + return ret; + + if (!stfm1000->pilot_present) { + ret = stfm1000_clear_bits(stfm1000, + STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + + /* Reset weak signal flag */ + stfm1000->weak_signal = 0; + stfm1000->prev_pilot_present = stfm1000->pilot_present; + } + + } + + if (stfm1000->revid == STFM1000_CHIP_REV_TA2) { + + /* read AGC_STAT register */ + ret = stfm1000_read(stfm1000, STFM1000_AGC_STAT, &tmp); + if (ret != 0) + return ret; + + agc_out = (tmp & STFM1000_AGCOUT_STAT_MASK) >> + STFM1000_AGCOUT_STAT_SHIFT; + lna_rms = (tmp & STFM1000_LNA_RMS_MASK) >> + STFM1000_LNA_RMS_SHIFT; + + ret = stfm1000_read(stfm1000, STFM1000_AGC_CONTROL1, &tmp); + if (ret != 0) + return ret; + + /* extract LNATH */ + lna_th = (tmp & STFM1000_B2_LNATH_MASK) >> + STFM1000_B2_LNATH_SHIFT; + + if (lna_rms > lna_th && agc_out <= 1) { + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + STFM1000_USEATTEN(1), STFM1000_USEATTEN_MASK); + if (ret != 0) + return ret; + + } else if (agc_out > 15) { + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + STFM1000_USEATTEN(0), STFM1000_USEATTEN_MASK); + if (ret != 0) + return ret; + } + } + + /* disable buffered writes */ + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + return ret; +} + +static int Is_Station(struct stfm1000 *stfm1000) +{ + u32 tmp, rssi_dc_est, tone_data; + u16 rssi_mantissa, rssi_exponent, rssi_decoded; + u16 prssi; + s16 mpx_dc; + int rssi_log; + + /* Get Rssi register readings from STFM1000 */ + stfm1000_read(stfm1000, STFM1000_RSSI_TONE, &tmp); + rssi_dc_est = tmp & 0xffff; + tone_data = (tmp >> 16) & 0x0fff; + + rssi_mantissa = (rssi_dc_est & 0xffe0) >> 5; /* 11Msb */ + rssi_exponent = rssi_dc_est & 0x001f; /* 5 lsb */ + rssi_decoded = (u32)rssi_mantissa << rssi_exponent; + + /* Convert Rsst to 10log(Rssi) */ + for (prssi = 20; prssi > 0; prssi--) + if (rssi_decoded >= (1 << prssi)) + break; + + rssi_log = (3 * rssi_decoded >> prssi) + (3 * prssi - 3); + /* clamp to positive */ + if (rssi_log < 0) + rssi_log = 0; + /* Compensate for errors in truncation/approximation by adding 1 */ + rssi_log++; + + stfm1000->rssi_dc_est_log = rssi_log; + stfm1000->signal_strength = stfm1000->rssi_dc_est_log; + + /* determine absolute value */ + if (tmp & 0x0800) + mpx_dc = ((tmp >> 16) & 0x0fff) | 0xf000; + else + mpx_dc = (tmp >> 16) & 0x0fff; + stfm1000->mpx_dc = mpx_dc; + mpx_dc = mpx_dc < 0 ? -mpx_dc : mpx_dc; + + if (stfm1000->tuning_grid_50KHz) + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th; + else + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th && + mpx_dc > stfm1000->tune_mpx_dc_th; + + return 0; +} + +int Monitor_STFM_AGC(struct stfm1000 *stfm1000) +{ + /* we don't do any AGC for now */ + return 0; +} + +static int Take_Down(struct stfm1000 *stfm1000) +{ + Mute_Audio(stfm1000); + + DRI_Off(stfm1000); + + SD_DP_Off(stfm1000); + + return 0; +} + +static int Bring_Up(struct stfm1000 *stfm1000) +{ + SD_DP_On(stfm1000); + + SD_Optimize_Channel(stfm1000); + + DRI_On(stfm1000); + + Unmute_Audio(stfm1000); + + if (stfm1000->rds_enable) + stfm1000_rds_reset(&stfm1000->rds_state); + + stfm1000->rds_sync = stfm1000->rds_enable; /* force sync (if RDS) */ + stfm1000->rds_demod_running = 0; + stfm1000->rssi_dc_est_log = 0; + stfm1000->signal_strength = 0; + + stfm1000->next_quality_monitor = jiffies + msecs_to_jiffies( + stfm1000->quality_monitor_period); + stfm1000->next_agc_monitor = jiffies + msecs_to_jiffies( + stfm1000->agc_monitor_period); + stfm1000->rds_pkt_bad = 0; + stfm1000->rds_pkt_good = 0; + stfm1000->rds_pkt_recovered = 0; + stfm1000->rds_pkt_lost_sync = 0; + stfm1000->rds_bit_overruns = 0; + + return 0; +} + +/* These are not used yet */ + +static int Lock_Station(struct stfm1000 *stfm1000) +{ + int ret; + + ret = SD_Optimize_Channel(stfm1000); + if (ret != 0) + return ret; + + /* AGC monitor start? */ + + return ret; +} + +static const struct stfm1000_reg sd_unlock_regs[] = { + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_REG_CLRBITS(PILOTTRACKING, STFM1000_B2_PILOTTRACKING_EN), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_END, +}; + +static int Unlock_Station(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_write_regs(stfm1000, sd_unlock_regs); + return ret; +} + +irqreturn_t stfm1000_dri_dma_irq(int irq, void *dev_id) +{ + struct stfm1000 *stfm1000 = dev_id; + u32 err_mask, irq_mask; + u32 ctrl; + int handled = 0; + +#ifdef CONFIG_ARCH_STMP37XX + err_mask = 1 << (16 + stfm1000->dma_ch); +#endif +#ifdef CONFIG_ARCH_STMP378X + err_mask = 1 << stfm1000->dma_ch; +#endif + irq_mask = 1 << stfm1000->dma_ch; + +#ifdef CONFIG_ARCH_STMP37XX + ctrl = HW_APBX_CTRL1_RD(); +#endif +#ifdef CONFIG_ARCH_STMP378X + ctrl = HW_APBX_CTRL2_RD(); +#endif + + if (ctrl & err_mask) { + handled = 1; + printk(KERN_WARNING "%s: DMA audio channel %d error\n", + __func__, stfm1000->dma_ch); +#ifdef CONFIG_ARCH_STMP37XX + HW_APBX_CTRL1_CLR(err_mask); +#endif +#ifdef CONFIG_ARCH_STMP378X + HW_APBX_CTRL2_CLR(err_mask); +#endif + } + + if (HW_APBX_CTRL1_RD() & irq_mask) { + handled = 1; + stmp3xxx_dma_clear_interrupt(stfm1000->dma_ch); + + if (stfm1000->alsa_initialized) { + BUG_ON(stfm1000_alsa_ops->dma_irq == NULL); + (*stfm1000_alsa_ops->dma_irq)(stfm1000); + } + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} +EXPORT_SYMBOL(stfm1000_dri_dma_irq); + +irqreturn_t stfm1000_dri_attn_irq(int irq, void *dev_id) +{ + struct stfm1000 *stfm1000 = dev_id; + int handled = 1; + u32 mask; + + (void)stfm1000; + mask = HW_DRI_CTRL_RD(); + mask &= BM_DRI_CTRL_OVERFLOW_IRQ | BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ | + BM_DRI_CTRL_ATTENTION_IRQ; + + HW_DRI_CTRL_CLR(mask); + + printk(KERN_INFO "DRI_ATTN:%s%s%s\n", + (mask & BM_DRI_CTRL_OVERFLOW_IRQ) ? " OV" : "", + (mask & BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ) ? " SL" : "", + (mask & BM_DRI_CTRL_ATTENTION_IRQ) ? " AT" : ""); + + if (stfm1000->alsa_initialized) { + BUG_ON(stfm1000_alsa_ops->attn_irq == NULL); + (*stfm1000_alsa_ops->attn_irq)(stfm1000); + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} +EXPORT_SYMBOL(stfm1000_dri_attn_irq); + +void stfm1000_decode_block(struct stfm1000 *stfm1000, const s16 *src, s16 *dst, + int count) +{ + int i; + + if (stfm1000->mute) { + memset(dst, 0, count * sizeof(s16) * 2); + return; + + } + + for (i = 0; i < count; i++, dst += 2, src += 4) { + + stfm1000_filter_decode(&stfm1000->filter_parms, + src[0], src[1], src[2]); + + dst[0] = stfm1000_filter_value_left(&stfm1000->filter_parms); + dst[1] = stfm1000_filter_value_right(&stfm1000->filter_parms); + } + + stfm1000->rssi = stfm1000->filter_parms.RssiDecoded; + stfm1000->stereo = stfm1000->pilot_present && + !stfm1000->filter_parms.pCoefForcedMono; + + /* RDS processing */ + if (stfm1000->rds_demod_running) { + /* rewind */ + src -= count * 4; + stfm1000_rds_demod(&stfm1000->rds_state, src, count); + } + +} +EXPORT_SYMBOL(stfm1000_decode_block); + +void stfm1000_take_down(struct stfm1000 *stfm1000) +{ + mutex_lock(&stfm1000->state_lock); + stfm1000->active = 0; + Take_Down(stfm1000); + mutex_unlock(&stfm1000->state_lock); +} +EXPORT_SYMBOL(stfm1000_take_down); + +void stfm1000_bring_up(struct stfm1000 *stfm1000) +{ + mutex_lock(&stfm1000->state_lock); + + stfm1000->active = 1; + + stfm1000_filter_reset(&stfm1000->filter_parms); + + Bring_Up(stfm1000); + + mutex_unlock(&stfm1000->state_lock); +} +EXPORT_SYMBOL(stfm1000_bring_up); + +void stfm1000_tune_current(struct stfm1000 *stfm1000) +{ + mutex_lock(&stfm1000->state_lock); + sw_tune(stfm1000, stfm1000->freq); + mutex_unlock(&stfm1000->state_lock); +} +EXPORT_SYMBOL(stfm1000_tune_current); + +/* Alternate ZIF Tunings to avoid EMI */ +const struct stfm1000_tune1 +stfm1000_board_emi_tuneups[STFM1000_FREQUENCY_100KHZ_RANGE] = { +#undef TUNE_ENTRY +#define TUNE_ENTRY(f, t1, sd) \ + [(f) - STFM1000_FREQUENCY_100KHZ_MIN] = \ + { .tune1 = (t1), .sdnom = (sd) } + TUNE_ENTRY(765, 0x84030, 0x1BF5E50D), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(780, 0x84240, 0x1BA5162F), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(795, 0x84250, 0x1C2D2F39), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(810, 0x84460, 0x1BDD207E), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(825, 0x84470, 0x1C6138CD), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(839, 0xC4680, 0x1C11F704), /* 061215 Jon, IF +100kHz */ + TUNE_ENTRY(840, 0x84680, 0x1c11f704), + TUNE_ENTRY(855, 0x84890, 0x1BC71C71), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(870, 0x848A0, 0x1C43DE10), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(885, 0x84AB0, 0x1BF9B021), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(899, 0xC4CC0, 0x1BB369A9), /* 061025 Arthur, IF +100kHz */ + TUNE_ENTRY(900, 0x84CC0, 0x1BB369A9), /* 061025 Arthur, IF 0kHz */ + TUNE_ENTRY(915, 0x84CD0, 0x1C299A5B), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(930, 0x84ee0, 0x1be3e6aa), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(945, 0x84ef0, 0x1c570f8b), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(959, 0xC5100, 0x1c11f704), + TUNE_ENTRY(960, 0x85100, 0x1c11f704), + TUNE_ENTRY(975, 0x85310, 0x1bd03d57), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(990, 0x85320, 0x1c3dc822), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1005, 0x85530, 0x1bfc93ff), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1019, 0xC5740, 0x1BBE683C), /* 061025 Arthur, IF +100kHz */ + TUNE_ENTRY(1020, 0x85740, 0x1bbe683c), /* 061025 Arthur, IF +0kHz */ + TUNE_ENTRY(1035, 0x85750, 0x1c26dab6), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1050, 0x85960, 0x1be922b4), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1065, 0x85970, 0x1c4f357c), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1079, 0xC5B80, 0x1c11f704), + TUNE_ENTRY(1080, 0x85B80, 0x1c11f704), +#undef TUNE_ENTRY +}; + +static const struct stfm1000_tune1 *stfm1000_board_emi_tune(int freq100) +{ + const struct stfm1000_tune1 *tune1; + + if ((unsigned int)(freq100 - STFM1000_FREQUENCY_100KHZ_MIN) >= + STFM1000_FREQUENCY_100KHZ_RANGE) + return NULL; + + tune1 = &stfm1000_board_emi_tuneups[freq100 - + STFM1000_FREQUENCY_100KHZ_MIN]; + if (tune1->tune1 == 0 && tune1->sdnom == 0) + return NULL; + return tune1; +} + +/* freq in kHz */ +static int sw_tune(struct stfm1000 *stfm1000, u32 freq) +{ + u32 freq100 = freq / 100; + int tune_cap; + int i2s_clock; + int mix_reg; + int if_freq, fe_freq; + u32 tune1, sdnom, agc1; + const struct stfm1000_tune1 *tp; + int ret; + + if_freq = 0; + mix_reg = 1; + switch (mix_reg) { + case 0: if_freq = -2; break; + case 1: if_freq = -1; break; + case 2: if_freq = 0; break; + case 3: if_freq = 1; break; + case 4: if_freq = 2; break; + } + + /* handle board specific EMI tuning */ + tp = stfm1000_board_emi_tune(freq100); + if (tp != NULL) { + tune1 = tp->tune1; + sdnom = tp->sdnom; + } else { + fe_freq = freq100 + if_freq; + + /* clamp into range */ + if (fe_freq < STFM1000_FREQUENCY_100KHZ_MIN) + fe_freq = STFM1000_FREQUENCY_100KHZ_MIN; + else if (fe_freq > STFM1000_FREQUENCY_100KHZ_MAX) + fe_freq = STFM1000_FREQUENCY_100KHZ_MAX; + + tp = &stfm1000_tune1_table[fe_freq - + STFM1000_FREQUENCY_100KHZ_MIN]; + + /* bits [14:0], [20:18] */ + tune1 = (tp->tune1 & 0x7fff) | (mix_reg << 18); + sdnom = tp->sdnom; + } + + agc1 = stfm1000->revid == STFM1000_CHIP_REV_TA2 ? 0x0400 : 0x2200; + + ret = stfm1000_write_masked(stfm1000, STFM1000_AGC_CONTROL1, + agc1, 0x3f00); + if (ret != 0) + goto err; + + ret = stfm1000_write_masked(stfm1000, STFM1000_TUNE1, tune1, + 0xFFFF7FFF); /* do not set bit-15 */ + if (ret != 0) + goto err; + + /* keep this around */ + stfm1000->sdnominal_pivot = sdnom; + + ret = stfm1000_write(stfm1000, STFM1000_SDNOMINAL, sdnom); + if (ret != 0) + goto err; + + /* fix for seek-not-stopping on alternate tunings */ + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + goto err; + + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + goto err; + + ret = stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DRI_CLK_EN); + if (ret != 0) + goto err; + + /* 6MHz spur fix */ + if ((freq100 >= 778 && freq100 <= 782) || + (freq100 >= 838 && freq100 <= 842) || + (freq100 >= 898 && freq100 <= 902) || + (freq100 >= 958 && freq100 <= 962) || + (freq100 >= 1018 && freq100 <= 1022) || + (freq100 >= 1078 && freq100 <= 1080)) + i2s_clock = 5; /* 4.8MHz */ + else + i2s_clock = 4; + + ret = stfm1000_write_masked(stfm1000, STFM1000_DATAPATH, + STFM1000_SAI_CLK_DIV(i2s_clock), STFM1000_SAI_CLK_DIV_MASK); + if (ret != 0) + goto err; + + ret = stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DRI_CLK_EN); + if (ret != 0) + goto err; + + if (tune1 & 0xf) + ret = stfm1000_set_bits(stfm1000, STFM1000_CLK1, + STFM1000_ENABLE_TAPDELAYFIX); + else + ret = stfm1000_clear_bits(stfm1000, STFM1000_CLK1, + STFM1000_ENABLE_TAPDELAYFIX); + + if (ret != 0) + goto err; + + tune_cap = (int)(stfm1000->tune_cap_a_f - + stfm1000->tune_cap_b_f * freq100); + if (tune_cap < 4) + tune_cap = 4; + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + STFM1000_ANTENNA_TUNECAP(tune_cap), + STFM1000_ANTENNA_TUNECAP_MASK); + if (ret != 0) + goto err; + + /* set signal strenth to 0 */ + /* stfm1000_dcdc_update(); */ + + /* cmp_rds_setRdsStatus(0) */ + /* cmp_rds_ResetGroupCallbacks(); */ + stfm1000->freq = freq; + + return 0; +err: + return -1; +} + +static const struct v4l2_queryctrl radio_qctrl[] = { + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + strlcpy(v->driver, "radio-stfm1000", sizeof(v->driver)); + strlcpy(v->card, "STFM1000 Radio", sizeof(v->card)); + sprintf(v->bus_info, "i2c"); + v->version = KERNEL_VERSION(0, 0, 1); + v->capabilities = V4L2_CAP_TUNER; + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + u32 tmp, rssi_dc_est, tone_data; + u16 rssi_mantissa, rssi_exponent, rssi_decoded; + u16 prssi; + s16 mpx_dc; + int rssi_log; + int ret; + + if (v->index > 0) + return -EINVAL; + + mutex_lock(&stfm1000->state_lock); + + strcpy(v->name, "FM"); + v->type = V4L2_TUNER_RADIO; + v->rangelow = (u32)(87.5 * 16000); + v->rangehigh = (u32)(108 * 16000); + v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + v->capability = V4L2_TUNER_CAP_LOW; + v->audmode = V4L2_TUNER_MODE_STEREO; + v->signal = 0; /* tr_getsigstr(); */ + + msleep(50); + + ret = stfm1000_read(stfm1000, STFM1000_RSSI_TONE, &tmp); + if (ret != 0) + goto out; + + rssi_dc_est = tmp & 0xffff; + tone_data = (tmp >> 16) & 0x0fff; + + rssi_mantissa = (rssi_dc_est & 0xffe0) >> 5; /* 11Msb */ + rssi_exponent = rssi_dc_est & 0x001f; /* 5 lsb */ + rssi_decoded = (u32)rssi_mantissa << rssi_exponent; + + /* Convert Rsst to 10log(Rssi) */ + for (prssi = 20; prssi > 0; prssi--) + if (rssi_decoded >= (1 << prssi)) + break; + + rssi_log = (3 * rssi_decoded >> prssi) + (3 * prssi - 3); + /* clamp to positive */ + if (rssi_log < 0) + rssi_log = 0; + /* Compensate for errors in truncation/approximation by adding 1 */ + rssi_log++; + + stfm1000->rssi_dc_est_log = rssi_log; + stfm1000->signal_strength = stfm1000->rssi_dc_est_log; + + /* determine absolute value */ + if (tmp & 0x0800) + mpx_dc = ((tmp >> 16) & 0x0fff) | 0xf000; + else + mpx_dc = (tmp >> 16) & 0x0fff; + stfm1000->mpx_dc = mpx_dc; + mpx_dc = mpx_dc < 0 ? -mpx_dc : mpx_dc; + + v->signal = rssi_decoded & 0xffff; + +out: + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + if (v->index > 0) + return -EINVAL; + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + mutex_lock(&stfm1000->state_lock); + + /* convert from the crazy linux value to our decimal based values */ + stfm1000->freq = (u32)div_u64((u64)(125 * (u64)f->frequency), 2000); + + if (stfm1000->active) + Take_Down(stfm1000); + + sw_tune(stfm1000, stfm1000->freq); + + if (stfm1000->active) + Bring_Up(stfm1000); + + mutex_unlock(&stfm1000->state_lock); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + f->type = V4L2_TUNER_RADIO; + f->frequency = stfm1000->freq * 16; + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + int i; + + (void)stfm1000; + + for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { + if (qc->id && qc->id == radio_qctrl[i].id) { + memcpy(qc, &radio_qctrl[i], sizeof(*qc)); + return 0; + } + } + return -EINVAL; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + switch (ctrl->id) { + + case V4L2_CID_AUDIO_MUTE: + ctrl->value = stfm1000->mute; + return 0; + + } + return -EINVAL; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + int ret; + + mutex_lock(&stfm1000->state_lock); + + ret = -EINVAL; + + switch (ctrl->id) { + + case V4L2_CID_AUDIO_MUTE: + stfm1000->mute = ctrl->value; + ret = 0; + break; + } + + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +static int vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + if (a->index > 1) + return -EINVAL; + + strcpy(a->name, "Radio"); + a->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + if (a->index > 1) + return -EINVAL; + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + *i = 0; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + if (i != 0) + return -EINVAL; + + return 0; +} + +const struct v4l2_ioctl_ops stfm_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_audio = vidioc_g_audio, + .vidioc_s_audio = vidioc_s_audio, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, +}; + +static int stfm1000_open(struct inode *inode, struct file *file) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + mutex_lock(&stfm1000->state_lock); + stfm1000->users = 1; + mutex_unlock(&stfm1000->state_lock); + + return 0; +} +static int stfm1000_close(struct inode *inode, struct file *file) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + if (!stfm1000) + return -ENODEV; + + stfm1000->users = 0; + if (stfm1000->removed) + kfree(stfm1000); + return 0; +} + +static const struct file_operations stfm1000_fops = { + .owner = THIS_MODULE, + .open = stfm1000_open, + .release = stfm1000_close, + .ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl = v4l_compat_ioctl32, +#endif + .llseek = no_llseek, +}; + +/* sysfs */ + +#define STFM1000_RO_ATTR(var) \ +static ssize_t stfm1000_show_ ## var(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(d); \ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); \ + return sprintf(buf, "%d\n", stfm1000->var); \ +} \ +static DEVICE_ATTR(var, 0444, stfm1000_show_ ##var, NULL) + +#define STFM1000_RW_ATTR(var) \ +static ssize_t stfm1000_show_ ## var(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(d); \ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); \ + return sprintf(buf, "%u\n", stfm1000->var); \ +} \ +static ssize_t stfm1000_store_ ## var(struct device *d, \ + struct device_attribute *attr, const char *buf, size_t size) \ +{ \ + struct i2c_client *client = to_i2c_client(d); \ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); \ + unsigned long v; \ + \ + strict_strtoul(buf, 0, &v); \ + stfm1000_commit_ ## var(stfm1000, v); \ + return size; \ +} \ +static DEVICE_ATTR(var, 0644, stfm1000_show_ ##var, stfm1000_store_ ##var) + +#define STFM1000_RW_ATTR_SIMPLE(var) \ +static void stfm1000_commit_ ## var(struct stfm1000 *stfm1000, \ + unsigned long value) \ +{ \ + stfm1000->var = value; \ +} \ +STFM1000_RW_ATTR(var) + +STFM1000_RO_ATTR(weak_signal); +STFM1000_RO_ATTR(pilot_present); +STFM1000_RO_ATTR(stereo); +STFM1000_RO_ATTR(rssi); +STFM1000_RO_ATTR(mpx_dc); +STFM1000_RO_ATTR(signal_strength); +STFM1000_RW_ATTR_SIMPLE(rds_signal_th); +STFM1000_RO_ATTR(rds_present); +STFM1000_RO_ATTR(is_station); + +static void stfm1000_commit_georegion(struct stfm1000 *stfm1000, + unsigned long value) +{ + /* don't do anything for illegal region */ + if (value != 0 && value != 1) + return; + + mutex_lock(&stfm1000->state_lock); + + stfm1000->georegion = value; + if (stfm1000->georegion == 0) + stfm1000_clear_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DEEMPH_50_75B); + else + stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DEEMPH_50_75B); + + mutex_unlock(&stfm1000->state_lock); +} +STFM1000_RW_ATTR(georegion); + +static void stfm1000_commit_freq(struct stfm1000 *stfm1000, + unsigned long value) +{ + mutex_lock(&stfm1000->state_lock); + + /* clamp */ + if (value < STFM1000_FREQUENCY_100KHZ_MIN * 100) + value = STFM1000_FREQUENCY_100KHZ_MIN * 100; + else if (value > STFM1000_FREQUENCY_100KHZ_MAX * 100) + value = STFM1000_FREQUENCY_100KHZ_MAX * 100; + + stfm1000->freq = value; + + if (stfm1000->active) + Take_Down(stfm1000); + + sw_tune(stfm1000, stfm1000->freq); + + if (stfm1000->active) + Bring_Up(stfm1000); + + mutex_unlock(&stfm1000->state_lock); +} +STFM1000_RW_ATTR(freq); + +static void stfm1000_commit_mute(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->mute = !!value; +} +STFM1000_RW_ATTR(mute); + +static void stfm1000_commit_force_mono(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->force_mono = !!value; + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = stfm1000->force_mono; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; +} +STFM1000_RW_ATTR(force_mono); + +STFM1000_RW_ATTR_SIMPLE(monitor_period); +STFM1000_RW_ATTR_SIMPLE(quality_monitor); +STFM1000_RW_ATTR_SIMPLE(quality_monitor_period); +STFM1000_RW_ATTR_SIMPLE(agc_monitor_period); +STFM1000_RW_ATTR_SIMPLE(tune_rssi_th); +STFM1000_RW_ATTR_SIMPLE(tune_mpx_dc_th); + +static void stfm1000_commit_rds_enable(struct stfm1000 *stfm1000, + unsigned long value) +{ + /* don't do anything for illegal values (or for not TB2) */ + if ((value != 0 && value != 1) || + stfm1000->revid == STFM1000_CHIP_REV_TA2) + return; + + mutex_lock(&stfm1000->state_lock); + + stfm1000->rds_enable = value; + if (stfm1000->rds_enable == 0) + stfm1000_clear_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_ENABLE); + else + stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_ENABLE); + + mutex_unlock(&stfm1000->state_lock); +} +STFM1000_RW_ATTR(rds_enable); + +static void stfm1000_commit_rds_sync(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->rds_sync = stfm1000->rds_enable && !!value; +} +STFM1000_RW_ATTR(rds_sync); + +STFM1000_RW_ATTR_SIMPLE(rds_pkt_good); +STFM1000_RW_ATTR_SIMPLE(rds_pkt_bad); +STFM1000_RW_ATTR_SIMPLE(rds_pkt_recovered); +STFM1000_RW_ATTR_SIMPLE(rds_pkt_lost_sync); +STFM1000_RW_ATTR_SIMPLE(rds_bit_overruns); +STFM1000_RW_ATTR_SIMPLE(rds_info); + +static void stfm1000_commit_rds_sdnominal_adapt(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->rds_sdnominal_adapt = !!value; + stfm1000->rds_state.demod.sdnom_adapt = stfm1000->rds_sdnominal_adapt; +} +STFM1000_RW_ATTR(rds_sdnominal_adapt); + +static void stfm1000_commit_rds_phase_pop(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->rds_phase_pop = !!value; + stfm1000->rds_state.demod.PhasePoppingEnabled = + stfm1000->rds_phase_pop; +} +STFM1000_RW_ATTR(rds_phase_pop); + +STFM1000_RW_ATTR_SIMPLE(tuning_grid_50KHz); + +static ssize_t stfm1000_show_rds_ps(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(d); + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); + char ps[9]; + + if (stfm1000_rds_get_ps(&stfm1000->rds_state, ps, sizeof(ps)) <= 0) + ps[0] = '\0'; + + return sprintf(buf, "%s\n", ps); +} +static DEVICE_ATTR(rds_ps, 0444, stfm1000_show_rds_ps, NULL); + +static ssize_t stfm1000_show_rds_text(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(d); + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); + char text[65]; + + if (stfm1000_rds_get_text(&stfm1000->rds_state, text, + sizeof(text)) <= 0) + text[0] = '\0'; + + return sprintf(buf, "%s\n", text); +} +static DEVICE_ATTR(rds_text, 0444, stfm1000_show_rds_text, NULL); + +static struct device_attribute *stfm1000_attrs[] = { + &dev_attr_agc_monitor_period, + &dev_attr_force_mono, + &dev_attr_freq, + &dev_attr_georegion, + &dev_attr_is_station, + &dev_attr_monitor_period, + &dev_attr_mpx_dc, + &dev_attr_mute, + &dev_attr_pilot_present, + &dev_attr_quality_monitor, + &dev_attr_quality_monitor_period, + &dev_attr_rds_bit_overruns, + &dev_attr_rds_enable, + &dev_attr_rds_info, + &dev_attr_rds_phase_pop, + &dev_attr_rds_pkt_bad, + &dev_attr_rds_pkt_good, + &dev_attr_rds_pkt_lost_sync, + &dev_attr_rds_pkt_recovered, + &dev_attr_rds_present, + &dev_attr_rds_ps, + &dev_attr_rds_sdnominal_adapt, + &dev_attr_rds_signal_th, + &dev_attr_rds_sync, + &dev_attr_rds_text, + &dev_attr_rssi, + &dev_attr_signal_strength, + &dev_attr_stereo, + &dev_attr_tune_mpx_dc_th, + &dev_attr_tune_rssi_th, + &dev_attr_tuning_grid_50KHz, + &dev_attr_weak_signal, + NULL, +}; + +/* monitor thread */ + +static void rds_process(struct stfm1000 *stfm1000) +{ + int count, bit; + int mix_reg, sdnominal_reg; + u32 sdnom, sdnom_new, limit; + u8 buf[8]; + + if (!stfm1000->rds_enable) + return; + + if (stfm1000->rds_sync && + stfm1000->rssi_dc_est_log > stfm1000->rds_signal_th) { + if (stfm1000->rds_info) + printk(KERN_INFO "RDS: sync\n"); + stfm1000_rds_reset(&stfm1000->rds_state); + stfm1000->rds_demod_running = 1; + stfm1000->rds_sync = 0; + } + + if (!stfm1000->rds_demod_running) + return; + + /* process mix reg requests */ + spin_lock_irq(&stfm1000->rds_lock); + mix_reg = stfm1000_rds_mix_msg_get(&stfm1000->rds_state); + spin_unlock_irq(&stfm1000->rds_lock); + + if (mix_reg != -1) { + + if (stfm1000->rds_info) + printk(KERN_INFO "RDS: new RDS_MIXOFFSET %d\n", + mix_reg & 1); + + /* update register */ + if (mix_reg & 1) + stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_MIXOFFSET); + else + stfm1000_clear_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_MIXOFFSET); + + /* signal it's processed */ + spin_lock_irq(&stfm1000->rds_lock); + stfm1000_rds_mix_msg_processed(&stfm1000->rds_state, mix_reg); + spin_unlock_irq(&stfm1000->rds_lock); + } + + /* process sdnominal reg requests */ + spin_lock_irq(&stfm1000->rds_lock); + sdnominal_reg = stfm1000_rds_sdnominal_msg_get(&stfm1000->rds_state); + spin_unlock_irq(&stfm1000->rds_lock); + + /* any change? */ + if (sdnominal_reg != 0) { + + stfm1000_read(stfm1000, STFM1000_SDNOMINAL, &sdnom); + + sdnom_new = sdnom + sdnominal_reg; + + /* Limit SDNOMINAL to within 244 ppm of its ideal value */ + limit = stfm1000->sdnominal_pivot + + (stfm1000->sdnominal_pivot >> 12); + if (sdnom_new > limit) + sdnom_new = limit; + + limit = stfm1000->sdnominal_pivot - + (stfm1000->sdnominal_pivot >> 12); + if (sdnom_new < limit) + sdnom_new = limit; + + /* write the register */ + stfm1000_write(stfm1000, STFM1000_SDNOMINAL, sdnom_new); + + /* signal it's processed */ + spin_lock_irq(&stfm1000->rds_lock); + stfm1000_rds_sdnominal_msg_processed(&stfm1000->rds_state, + sdnominal_reg); + spin_unlock_irq(&stfm1000->rds_lock); + } + + /* pump bits out & pass them to the process function */ + spin_lock_irq(&stfm1000->rds_lock); + while (stfm1000_rds_bits_available(&stfm1000->rds_state) > 128) { + count = 0; + while (count++ < 128 && + (bit = stmf1000_rds_get_bit( + &stfm1000->rds_state)) >= 0) { + spin_unlock_irq(&stfm1000->rds_lock); + + /* push bit for packet processing */ + stfm1000_rds_packet_bit(&stfm1000->rds_state, bit); + + spin_lock_irq(&stfm1000->rds_lock); + } + } + spin_unlock_irq(&stfm1000->rds_lock); + + /* now we're free to process non-interrupt related work */ + while (stfm1000_rds_packet_dequeue(&stfm1000->rds_state, buf) == 0) { + + if (stfm1000->rds_info) + printk(KERN_INFO "RDS-PKT: %02x %02x %02x %02x " + "%02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + + stfm1000_rds_process_packet(&stfm1000->rds_state, buf); + } + + /* update our own counters */ + stfm1000->rds_pkt_good += stfm1000->rds_state.pkt.good_packets; + stfm1000->rds_pkt_bad += stfm1000->rds_state.pkt.bad_packets; + stfm1000->rds_pkt_recovered += + stfm1000->rds_state.pkt.recovered_packets; + stfm1000->rds_pkt_lost_sync += + stfm1000->rds_state.pkt.sync_lost_packets; + stfm1000->rds_bit_overruns += + stfm1000->rds_state.demod.RdsDemodSkippedBitCnt; + + /* zero them now */ + stfm1000->rds_state.pkt.good_packets = 0; + stfm1000->rds_state.pkt.bad_packets = 0; + stfm1000->rds_state.pkt.recovered_packets = 0; + stfm1000->rds_state.pkt.sync_lost_packets = 0; + stfm1000->rds_state.demod.RdsDemodSkippedBitCnt = 0; + + /* reset requested from RDS handler? */ + if (stfm1000_rds_get_reset_req(&stfm1000->rds_state)) { + if (stfm1000->rds_info) + printk(KERN_INFO "RDS: reset requested\n"); + stfm1000_rds_reset(&stfm1000->rds_state); + + stfm1000->rds_sync = stfm1000->rds_enable; /* force sync (if RDS) */ + stfm1000->rds_demod_running = 0; + stfm1000->rssi_dc_est_log = 0; + stfm1000->signal_strength = 0; + } +} + +void stfm1000_monitor_signal(struct stfm1000 *stfm1000, int bit) +{ + set_bit(bit, &stfm1000->thread_events); + return wake_up_interruptible(&stfm1000->thread_wait); +} + +static int stfm1000_monitor_thread(void *data) +{ + struct stfm1000 *stfm1000 = data; + int ret; + + printk(KERN_INFO "stfm1000: monitor thread started\n"); + + set_freezable(); + + /* Hmm, linux becomes *very* unhappy without this ... */ + while (!kthread_should_stop()) { + + ret = wait_event_interruptible_timeout(stfm1000->thread_wait, + stfm1000->thread_events == 0, + msecs_to_jiffies(stfm1000->monitor_period)); + + stfm1000->thread_events = 0; + + if (kthread_should_stop()) + break; + + try_to_freeze(); + + mutex_lock(&stfm1000->state_lock); + + /* we must be active */ + if (!stfm1000->active) + goto next; + + if (stfm1000->rds_enable) + rds_process(stfm1000); + + /* perform quality monitor */ + if (time_after_eq(jiffies, stfm1000->next_quality_monitor)) { + + /* full quality monitor? */ + if (stfm1000->quality_monitor) + Monitor_STFM_Quality(stfm1000); + else /* simple */ + Is_Station(stfm1000); + + while (time_after_eq(jiffies, + stfm1000->next_quality_monitor)) + stfm1000->next_quality_monitor += + msecs_to_jiffies( + stfm1000->quality_monitor_period); + } + + /* perform AGC monitor (if enabled) */ + if (stfm1000->agc_monitor && time_after_eq(jiffies, + stfm1000->next_agc_monitor)) { + Monitor_STFM_AGC(stfm1000); + while (time_after_eq(jiffies, + stfm1000->next_agc_monitor)) + stfm1000->next_agc_monitor += + msecs_to_jiffies( + stfm1000->agc_monitor_period); + } +next: + mutex_unlock(&stfm1000->state_lock); + } + + printk(KERN_INFO "stfm1000: monitor thread stopped\n"); + + return 0; +} + +static u64 stfm1000_dma_mask = DMA_32BIT_MASK; + +static int stfm1000_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct device *dev; + struct stfm1000 *stfm1000; + struct video_device *vd; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + u32 id; + const char *idtxt; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C doesn't support I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + /* make sure the dma masks are set correctly */ + dev = &client->dev; + if (!dev->dma_mask) + dev->dma_mask = &stfm1000_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_32BIT_MASK; + + stfm1000 = kzalloc(sizeof(*stfm1000), GFP_KERNEL); + if (!stfm1000) + return -ENOMEM; + + stfm1000->client = client; + i2c_set_clientdata(client, stfm1000); + + mutex_init(&stfm1000->xfer_lock); + mutex_init(&stfm1000->state_lock); + + vd = &stfm1000->radio; + + strcpy(vd->name, "stfm1000"); + vd->vfl_type = VID_TYPE_TUNER; + vd->fops = &stfm1000_fops; + vd->ioctl_ops = &stfm_ioctl_ops; + + /* vd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG; */ + + vd->parent = &client->dev; + + ret = video_register_device(vd, VFL_TYPE_RADIO, -1); + if (ret != 0) { + dev_warn(&adapter->dev, + "Cannot register radio device\n"); + goto out; + } + + spin_lock_init(&stfm1000->rds_lock); + + stfm1000_setup_reg_set(stfm1000); + + /* stfm1000->dbgflg |= STFM1000_DBGFLG_I2C; */ + + ret = stfm1000_read(stfm1000, STFM1000_CHIPID, &id); + if (ret < 0) { + dev_warn(&adapter->dev, + "Cannot read ID register\n"); + goto out; + } + stfm1000->revid = id & 0xff; + + /* NOTE: the tables are precalculated */ + stfm1000->tune_rssi_th = 28; + stfm1000->tune_mpx_dc_th = 300; + stfm1000->adj_chan_th = 100; + stfm1000->pilot_est_th = 25; + stfm1000->agc_monitor = 0; /* AGC monitor disabled */ + stfm1000->quality_monitor = 1; + stfm1000->weak_signal = 0; + stfm1000->prev_pilot_present = 0; + stfm1000->tune_cap_a_f = (u32)(72.4 * 65536); + stfm1000->tune_cap_b_f = (u32)(0.07 * 65536); + + /* only TB2 supports RDS */ + stfm1000->rds_enable = stfm1000->revid == STFM1000_CHIP_REV_TB2 && + rds_enable; + stfm1000->rds_present = 0; + stfm1000->rds_signal_th = 33; + + stfm1000->freq = 92600; + + stfm1000->georegion = georegion; + stfm1000->rssi = 0; + stfm1000->stereo = 0; + stfm1000->force_mono = 0; + stfm1000->monitor_period = 100; + stfm1000->quality_monitor_period = 1000; + stfm1000->agc_monitor_period = 200; + + stfm1000->rds_sdnominal_adapt = 0; + stfm1000->rds_phase_pop = 1; + + /* enable info about RDS */ + stfm1000->rds_info = 0; + + ret = stfm1000_power_up(stfm1000); + if (ret != 0) { + printk(KERN_ERR "%s: stfm1000_power_up failed\n", + __func__); + goto out; + } + + if (stfm1000_alsa_ops && stfm1000_alsa_ops->init) { + ret = (*stfm1000_alsa_ops->init)(stfm1000); + if (ret != 0) + goto out; + stfm1000->alsa_initialized = 1; + } + + ret = 0; + for (i = 0; stfm1000_attrs[i]; i++) { + ret = device_create_file(dev, stfm1000_attrs[i]); + if (ret) + break; + } + if (ret) { + while (--i >= 0) + device_remove_file(dev, stfm1000_attrs[i]); + goto out; + } + + /* add it to the list */ + mutex_lock(&devlist_lock); + stfm1000->idx = stfm1000_devcount++; + list_add_tail(&stfm1000->devlist, &stfm1000_devlist); + mutex_unlock(&devlist_lock); + + init_waitqueue_head(&stfm1000->thread_wait); + stfm1000->thread = kthread_run(stfm1000_monitor_thread, stfm1000, + "stfm1000-%d", stfm1000->idx); + if (stfm1000->thread == NULL) { + printk(KERN_ERR "stfm1000: kthread_run failed\n"); + goto out; + } + + idtxt = stfm1000_get_rev_txt(stfm1000->revid); + if (idtxt == NULL) + printk(KERN_INFO "STFM1000: Loaded for unknown revision id " + "0x%02x\n", stfm1000->revid); + else + printk(KERN_INFO "STFM1000: Loaded for revision %s\n", idtxt); + + return 0; + +out: + kfree(stfm1000); + return ret; +} + +static int stfm1000_remove(struct i2c_client *client) +{ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int i; + + kthread_stop(stfm1000->thread); + + for (i = 0; stfm1000_attrs[i]; i++) + device_remove_file(dev, stfm1000_attrs[i]); + + if (stfm1000->alsa_initialized) { + BUG_ON(stfm1000_alsa_ops->release == NULL); + (*stfm1000_alsa_ops->release)(stfm1000); + stfm1000->alsa_initialized = 0; + } + + stfm1000_power_down(stfm1000); + + video_unregister_device(&stfm1000->radio); + + mutex_lock(&devlist_lock); + list_del(&stfm1000->devlist); + mutex_unlock(&devlist_lock); + + kfree(stfm1000); + return 0; +} + +static const struct i2c_device_id stfm1000_id[] = { + { "stfm1000", 0xC0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stfm1000_id); + +static struct i2c_driver stfm1000_i2c_driver = { + .driver = { + .name = "stfm1000", + }, + .probe = stfm1000_probe, + .remove = stfm1000_remove, + .id_table = stfm1000_id, +}; + +static int __init +stfm1000_init(void) +{ + /* pull those in */ + (void)Lock_Station; + (void)Unlock_Station; + return i2c_add_driver(&stfm1000_i2c_driver); +} + +static void __exit +stfm1000_exit(void) +{ + i2c_del_driver(&stfm1000_i2c_driver); + + stfm1000_alsa_ops = NULL; +} + +module_init(stfm1000_init); +module_exit(stfm1000_exit); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("A driver for the STFM1000 chip."); +MODULE_LICENSE("GPL"); + +module_param(georegion, int, 0400); +module_param(rds_enable, int, 0400); |