From eb1143a3c3eac47d4ff81b08153ed52eac279595 Mon Sep 17 00:00:00 2001 From: Yunsu Kim Date: Mon, 11 Jun 2018 13:42:25 +0900 Subject: [PATCH] [9610] media: radio: add FM radio code for FM enable Change-Id: I304fa251a7a07bdf7436dc6acc6b745d407223a8 Signed-off-by: Yunsu Kim --- drivers/media/radio/Kconfig | 6 + drivers/media/radio/Makefile | 1 + drivers/media/radio/s610/Kconfig | 11 + drivers/media/radio/s610/Makefile | 9 + drivers/media/radio/s610/fm_ctrl.c | 385 ++++ drivers/media/radio/s610/fm_ctrl.h | 19 + drivers/media/radio/s610/fm_low.c | 2499 ++++++++++++++++++++++ drivers/media/radio/s610/fm_low_ref.h | 163 ++ drivers/media/radio/s610/fm_low_struc.h | 487 +++++ drivers/media/radio/s610/fm_rds.c | 1553 ++++++++++++++ drivers/media/radio/s610/fm_rds.h | 2148 +++++++++++++++++++ drivers/media/radio/s610/radio-s610.c | 2609 +++++++++++++++++++++++ drivers/media/radio/s610/radio-s610.h | 553 +++++ 13 files changed, 10443 insertions(+) create mode 100644 drivers/media/radio/s610/Kconfig create mode 100644 drivers/media/radio/s610/Makefile create mode 100644 drivers/media/radio/s610/fm_ctrl.c create mode 100644 drivers/media/radio/s610/fm_ctrl.h create mode 100644 drivers/media/radio/s610/fm_low.c create mode 100644 drivers/media/radio/s610/fm_low_ref.h create mode 100644 drivers/media/radio/s610/fm_low_struc.h create mode 100644 drivers/media/radio/s610/fm_rds.c create mode 100644 drivers/media/radio/s610/fm_rds.h create mode 100644 drivers/media/radio/s610/radio-s610.c create mode 100644 drivers/media/radio/s610/radio-s610.h diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 192f36f2f4aa..0552b3eaa72c 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -12,6 +12,12 @@ menuconfig RADIO_ADAPTERS if RADIO_ADAPTERS && VIDEO_V4L2 +config RADIO_S5E9610 + tristate "SAMSUNG S5E9610 FM Radio" + depends on VIDEO_V4L2 + +source "drivers/media/radio/s610/Kconfig" + config RADIO_TEA575X tristate diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 37e6e8255b57..6b60c2ce51b8 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o obj-$(CONFIG_RADIO_WL128X) += wl128x/ obj-$(CONFIG_RADIO_TEA575X) += tea575x.o obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o +obj-$(CONFIG_RADIO_S5E9610) += s610/ shark2-objs := radio-shark2.o radio-tea5777.o diff --git a/drivers/media/radio/s610/Kconfig b/drivers/media/radio/s610/Kconfig new file mode 100644 index 000000000000..fb3d40579e58 --- /dev/null +++ b/drivers/media/radio/s610/Kconfig @@ -0,0 +1,11 @@ +# +# SAMSUNG's S610 FM driver based on SPEEDY driver. +# +menu "Samsung S610 FM driver (SPEEDY based)" +config RADIO_S610 + tristate "Samsung S610 FM Radio" + depends on VIDEO_V4L2 && RADIO_S5E9610 + help + Choose Y here if you have this FM radio chip. + +endmenu diff --git a/drivers/media/radio/s610/Makefile b/drivers/media/radio/s610/Makefile new file mode 100644 index 000000000000..36af407264d6 --- /dev/null +++ b/drivers/media/radio/s610/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for SAMSUNG Radio driver based s610 +# FM radio. +# +obj-$(CONFIG_RADIO_S610) += fm_s5e9610.o +fm_s5e9610-objs := radio-s610.o \ + fm_low.o \ + fm_ctrl.o \ + fm_rds.o diff --git a/drivers/media/radio/s610/fm_ctrl.c b/drivers/media/radio/s610/fm_ctrl.c new file mode 100644 index 000000000000..a451fd051689 --- /dev/null +++ b/drivers/media/radio/s610/fm_ctrl.c @@ -0,0 +1,385 @@ +/**************************************************************************** + Copyright (C) 2015 Samsung Electronics Co., Ltd. All rights reserved. + + ******************************************************************************/ + +#include "fm_low_struc.h" +#include "radio-s610.h" +#include "fm_ctrl.h" + +extern struct s610_radio *gradio; + +void fm_pwron(void) +{ + fmspeedy_set_reg_field(0xFFF226, 0, 0x0001, 1); /* FM reset assert */ + fmspeedy_set_reg(0xFFF212, 0); /* last power on */ + fmspeedy_set_reg(0xFFF211, 0); /* first power on */ + fmspeedy_set_reg_field(0xFFF227, 0, 0x0001, 1); /* FM reset deassert */ + fmspeedy_set_reg(0xFFF210, 0); /* FM isolaton disable */ +} + +void fm_pwroff(void) +{ + fmspeedy_set_reg_field(0xFFF226, 0, 0x0001, 1); /* FM reset assert */ + fmspeedy_set_reg(0xFFF210, 1); /* FM isolaton enable */ + fmspeedy_set_reg(0xFFF211, 1); /* first power off */ + fmspeedy_set_reg(0xFFF212, 1); /* last power off */ +} + +void fmspeedy_wakeup(void) +{ + write32(gradio->fmspeedy_base + FMSPDY_CTL, SPDY_WAKEUP); + udelay(5); +} + +void fm_en_speedy_m_int(void) +{ + SetBits(gradio->fmspeedy_base + FMSPDY_INTR_MASK, + FM_SLV_INT_MASK_BIT, 1, 0); +} + +void fm_dis_speedy_m_int(void) +{ + SetBits(gradio->fmspeedy_base + FMSPDY_INTR_MASK, + FM_SLV_INT_MASK_BIT, 1, 1); +} + +void fm_speedy_m_int_stat_clear(void) +{ + write32(gradio->fmspeedy_base + FMSPDY_STAT, 0x1F); +} + +void fm_speedy_m_int_stat_clear_all(void) +{ + write32(gradio->fmspeedy_base + FMSPDY_STAT, 0x7F); +} + +void fm_speedy_m_int_enable(void) +{ + fm_en_speedy_m_int(); + fm_speedy_m_int_stat_clear_all(); +} + +void fm_speedy_m_int_disable(void) +{ + fm_dis_speedy_m_int(); + fm_speedy_m_int_stat_clear_all(); +} + +u32 fmspeedy_get_reg_core(u32 addr) +{ + u16 jj = 0; + u32 status1; + u32 ret = 0; + + fm_dis_speedy_m_int(); + + fm_speedy_m_int_stat_clear(); + write32(gradio->fmspeedy_base + FMSPDY_CMD, + FMSPDY_READ | FMSPDY_RANDOM + | ((addr & 0x1FF) << 7)); + + for (jj = 0; jj < 100; jj++) { + udelay(2); + status1 = read32(gradio->fmspeedy_base + FMSPDY_STAT); + if ((status1 & STAT_DONE) == 1) + break; + } + + if (jj >= 99) { + dev_err(gradio->dev, "%s(), Fail addr:0x%xh\n", + __func__, addr); + ret = -1; + goto get_fail; + } + + ret = read32(gradio->fmspeedy_base + FMSPDY_DATA); + +get_fail: + fm_en_speedy_m_int(); + + return ret; + +} + +u32 fmspeedy_get_reg(u32 addr) +{ + u32 data; + + API_ENTRY(gradio); + + spin_lock_irq(&gradio->slock); + + atomic_set(&gradio->is_doing, 1); + data = fmspeedy_get_reg_core(addr); + if (data == -1) + gradio->speedy_error++; + atomic_set(&gradio->is_doing, 0); + + spin_unlock_irq(&gradio->slock); + + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x]", __func__, addr, data); + API_EXIT(gradio); + return data; +} + +u32 fmspeedy_get_reg_work(u32 addr) +{ + u32 data; + + API_ENTRY(gradio); + + data = fmspeedy_get_reg_core(addr); + if (data == -1) + gradio->speedy_error++; + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x]", __func__, addr, data); + API_EXIT(gradio); + return data; +} + +int fmspeedy_set_reg_core(u32 addr, u32 data) +{ + u16 jj; + u32 status1; + int ret = 0; + + fm_dis_speedy_m_int(); + + fm_speedy_m_int_stat_clear(); + write32(gradio->fmspeedy_base + FMSPDY_DATA, data); + write32(gradio->fmspeedy_base + FMSPDY_CMD, + FMSPDY_WRITE | FMSPDY_RANDOM + | ((addr & 0x1FF) << 7)); + + for (jj = 0; jj < 100; jj++) { + udelay(2); + status1 = read32(gradio->fmspeedy_base + FMSPDY_STAT); + if ((status1 & STAT_DONE) == 1) + break; + } + + if (jj >= 99) { + dev_err(gradio->dev, "%s(), Fail addr:0x%xh, data:0x%xh\n", + __func__, addr, data); + ret = -1; + } + + fm_en_speedy_m_int(); + + return ret; +} + +int fmspeedy_set_reg(u32 addr, u32 data) +{ + int ret = 0; + + API_ENTRY(gradio); + + spin_lock_irq(&gradio->slock); + + atomic_set(&gradio->is_doing, 1); + ret = fmspeedy_set_reg_core(addr, data); + if (ret == -1) + gradio->speedy_error++; + atomic_set(&gradio->is_doing, 0); + + spin_unlock_irq(&gradio->slock); + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x], ret[0x%x]", __func__, addr, data, ret); + API_EXIT(gradio); + return ret; +} + +int fmspeedy_set_reg_work(u32 addr, u32 data) +{ + int ret = 0; + + API_ENTRY(gradio); + ret = fmspeedy_set_reg_core(addr, data); + if (ret == -1) + gradio->speedy_error++; + + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x], ret[0x%x]", __func__, addr, data, ret); + API_EXIT(gradio); + return ret; +} + +u32 fmspeedy_get_reg_field_core(u32 addr, u32 shift, u32 mask) +{ + u16 jj; + u32 status1; + u32 ret = 0; + + fm_dis_speedy_m_int(); + + fm_speedy_m_int_stat_clear(); + write32(gradio->fmspeedy_base + FMSPDY_CMD, + FMSPDY_READ | FMSPDY_RANDOM + | ((addr & 0x1FF) << 7)); + + for (jj = 0; jj < 100; jj++) { + udelay(2); + status1 = read32(gradio->fmspeedy_base + FMSPDY_STAT); + if ((status1 & STAT_DONE) == 1) + break; + } + + if (jj >= 99) { + dev_err(gradio->dev, "%s(), Fail addr:0x%xh\n", + __func__, addr); + ret = -1; + goto read_fail_f; + } + ret = (read32(gradio->fmspeedy_base + FMSPDY_DATA) & (mask)) >> shift; + +read_fail_f: + fm_en_speedy_m_int(); + + return ret; +} + +u32 fmspeedy_get_reg_field(u32 addr, u32 shift, u32 mask) +{ + u32 data; + + API_ENTRY(gradio); + + spin_lock_irq(&gradio->slock); + + atomic_set(&gradio->is_doing, 1); + data = fmspeedy_get_reg_field_core(addr, shift, mask); + if (data == -1) + gradio->speedy_error++; + atomic_set(&gradio->is_doing, 0); + + spin_unlock_irq(&gradio->slock); + + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x]", __func__, addr, data); + API_EXIT(gradio); + return data; +} + +u32 fmspeedy_get_reg_field_work(u32 addr, u32 shift, u32 mask) +{ + u32 data; + + API_ENTRY(gradio); + + data = fmspeedy_get_reg_field_core(addr, shift, mask); + if (data == -1) + gradio->speedy_error++; + + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x]", __func__, addr, data); + API_EXIT(gradio); + + return data; +} + +int fmspeedy_set_reg_field_core(u32 addr, u32 shift, u32 mask, u32 data) +{ + u32 value, value1; + u16 jj; + u32 status1; + int ret = 0; + + fm_dis_speedy_m_int(); + + fm_speedy_m_int_stat_clear(); + write32(gradio->fmspeedy_base + FMSPDY_CMD, + FMSPDY_READ | FMSPDY_RANDOM + | ((addr & 0x1FF) << 7)); + + for (jj = 0; jj < 100; jj++) { + udelay(2); + status1 = read32(gradio->fmspeedy_base + FMSPDY_STAT); + if ((status1 & STAT_DONE) == 1) + break; + } + + if (jj >= 99) { + dev_err(gradio->dev, "%s(), Fail addr:0x%xh, data:0x%xh, cnt:%d\n", + __func__, addr, data, jj); + ret = -1; + goto set_fail_f; + } + + value1 = read32(gradio->fmspeedy_base + FMSPDY_DATA); + value = (value1 & ~(mask)) | ((data) << (shift)); + + write32(gradio->fmspeedy_base + FMSPDY_DATA, value); + write32(gradio->fmspeedy_base + FMSPDY_STAT, 0x1F); + write32(gradio->fmspeedy_base + FMSPDY_CMD, + FMSPDY_WRITE | FMSPDY_RANDOM + | ((addr & 0x1FF) << 7)); + + for (jj = 0; jj < 100; jj++) { + udelay(2); + status1 = read32(gradio->fmspeedy_base + FMSPDY_STAT); + if ((status1 & STAT_DONE) == 1) + break; + } + + if (jj >= 99) { + dev_err(gradio->dev, "%s(), Fail addr:0x%xh, data:0x%xh, cnt:%d\n", + __func__, addr, data, jj); + ret = -1; + } + +set_fail_f: + fm_en_speedy_m_int(); + + return ret; +} + +int fmspeedy_set_reg_field(u32 addr, u32 shift, u32 mask, u32 data) +{ + int ret = 0; + + API_ENTRY(gradio); + spin_lock_irq(&gradio->slock); + + atomic_set(&gradio->is_doing, 1); + ret = fmspeedy_set_reg_field_core(addr, shift, mask, data); + if (ret == -1) + gradio->speedy_error++; + atomic_set(&gradio->is_doing, 0); + + spin_unlock_irq(&gradio->slock); + + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x], ret[0x%x]", __func__, addr, data, ret); + API_EXIT(gradio); + return ret; +} + +int fmspeedy_set_reg_field_work(u32 addr, u32 shift, u32 mask, u32 data) +{ + int ret = 0; + + API_ENTRY(gradio); + ret = fmspeedy_set_reg_field_core(addr, shift, mask, data); + if (ret == -1) + gradio->speedy_error++; + + APIEBUG(gradio, "%s():addr[0x%x], data[0x%x], ret[0x%x]", __func__, addr, data, ret); + API_EXIT(gradio); + + return ret; +} + +/**************************************************************************** + NAME + fm_audio_control - Audio out enable/disable + + FUNCTION + Setting registers for Audio + ****************************************************************************/ +void fm_audio_control(struct s610_radio *radio, + bool audio_out, bool lr_switch, + u32 req_time, u32 audio_addr) +{ + write32(radio->fmspeedy_base + AUDIO_CTRL, + ((audio_out << 21) | (lr_switch << 20) + | ((req_time & 0x07FF) << 9) + | (audio_addr & 0x01FF))); + udelay(15); +} + diff --git a/drivers/media/radio/s610/fm_ctrl.h b/drivers/media/radio/s610/fm_ctrl.h new file mode 100644 index 000000000000..5be98c29de40 --- /dev/null +++ b/drivers/media/radio/s610/fm_ctrl.h @@ -0,0 +1,19 @@ +void fm_pwron(void); +void fm_pwroff(void); +void fmspeedy_wakeup(void); +void fm_speedy_m_int_enable(void); +void fm_speedy_m_int_disable(void); +void fm_en_speedy_m_int(void); +void fm_dis_speedy_m_int(void); +void fm_speedy_m_int_stat_clear(void); +u32 fmspeedy_get_reg(u32 addr); +u32 fmspeedy_get_reg_work(u32 addr); +int fmspeedy_set_reg(u32 addr, u32 data); +int fmspeedy_set_reg_work(u32 addr, u32 data); +u32 fmspeedy_get_reg_field(u32 addr, u32 shift, u32 mask); +u32 fmspeedy_get_reg_field_work(u32 addr, u32 shift, u32 mask); +int fmspeedy_set_reg_field(u32 addr, u32 shift, u32 mask, u32 data); +int fmspeedy_set_reg_field_work(u32 addr, u32 shift, u32 mask, u32 data); +void fm_audio_control(struct s610_radio *radio, bool audio_out, bool lr_switch, + u32 req_time, u32 audio_addr); + diff --git a/drivers/media/radio/s610/fm_low.c b/drivers/media/radio/s610/fm_low.c new file mode 100644 index 000000000000..7e5a7bbe1036 --- /dev/null +++ b/drivers/media/radio/s610/fm_low.c @@ -0,0 +1,2499 @@ +/**************************************************************************** + FILE + */ +#include "fm_low_struc.h" +#include "radio-s610.h" +#include "fm_low_ref.h" + +extern struct s610_radio *gradio; + +/* Numeric identifier embedded in the code. */ +const u32 build_identifier_integer = 0x3ac6bb6b; +void (*handler_if_count)(struct s610_radio *radio) = NULL; +void (*handler_audio_pause)(struct s610_radio *radio) = NULL; +extern u32 *vol_level_init; +extern u32 *fm_spur_trf_init; +extern u32 *fm_dual_clk_init; + +/**************************************************************************** + + Functions for initialization + + ****************************************************************************/ + +int fm_boot(struct s610_radio *radio) +{ + bool aux_ret; + + fm_audio_control(radio, 0, 0, 0, 0); + + aux_ret = fm_aux_pll_initialize(); + if (!aux_ret) + return -1; + + /* power on for FM digital block */ + fm_pwron(); + + fm_lo_initialize(radio); + + fm_initialize(radio); + + return 0; +} + +void fm_power_off(void) +{ + fm_iclkaux_set(0); /* restore CLKMUX */ + + fm_lo_off(); + + /* power off for FM digital block */ + fm_pwroff(); + + fm_aux_pll_off(); +} + +void fm_iclkaux_set(u32 data) +{ + fmspeedy_set_reg_field(0xFFF220, 0, (0x0001<<0), data); /* iCLKAux */ + dev_info(gradio->dev, "%s: iClk Aux: 0x%xh get val: 0x%xh", __func__, + data, + fmspeedy_get_reg(0xFFF220)); +} + +void fm_initialize(struct s610_radio *radio) +{ + API_ENTRY(radio); + + /* Initialize the analogue block */ + fm_rx_init(); + + fm_iclkaux_set(radio->iclkaux); + + /* Set the demod reg. */ + if (radio->rfchip_ver == S620_REV_0) { + radio->low->fm_config.demod_conf_ini |= 0x2C000; + dev_info(radio->dev, "%s():demod_conf_ini[%08X]", + __func__, + radio->low->fm_config.demod_conf_ini); + } + fmspeedy_set_reg(0xFFF2A9, radio->low->fm_config.demod_conf_ini); + if (radio->vol_3db_att) + fmspeedy_set_reg_field(0xFFF2A9, 10, (0x01 << 10), 1); + else + fmspeedy_set_reg_field(0xFFF2A9, 10, (0x01 << 10), 0); + fmspeedy_set_reg(0xFFF2B9, radio->low->fm_config.narrow_thres_ini); + fmspeedy_set_reg(0xFFF2C6, radio->low->fm_config.snr_adj_ini); + fmspeedy_set_reg(0xFFF2CE, radio->low->fm_config.stereo_thres_ini); + + fmspeedy_set_reg(0xFFF2C8, radio->low->fm_config.snr_smooth_conf_ini); + fmspeedy_set_reg(0xFFF2C9, + radio->low->fm_config.soft_muffle_conf_ini.muffle_coeffs); + + fmspeedy_set_reg_field(0xFFF2AA, 3, (0x0007 << 3), + radio->low->fm_config.soft_mute_atten_max_ini); + fmspeedy_set_reg_field(0xFFF2AA, 0, 0x0007, + radio->low->fm_config.soft_muffle_conf_ini.lpf_bw); + fmspeedy_set_reg_field(0xFFF2AA, 6, (0x0001 << 6), + radio->low->fm_config.soft_muffle_conf_ini.lpf_en); + fmspeedy_set_reg_field(0xFFF2AA, 7, (0x0001 << 7), + radio->low->fm_config.soft_muffle_conf_ini.lpf_auto); + fmspeedy_set_reg_field(0xFFF2AA, 8, (0x0001 << 8), 1); + + if (!radio->without_elna) + radio->rssi_adjust = RSSI_ADJUST_WITHOUT_ELNA_VALUE; + + fmspeedy_set_reg(0xFFF2C2, radio->low->fm_config.rssi_adj_ini+radio->rssi_adjust); + APIEBUG(radio, "%s(): 0xFFF2C2:0x%x %d %d", __func__, + fmspeedy_get_reg(0xFFF2C2), radio->low->fm_config.rssi_adj_ini, radio->rssi_adjust); + + fmspeedy_set_reg(0xFFF299, 0xFF64); + +#ifdef USE_IQ_IMBAL_SMOOTH + fmspeedy_set_reg(0xFFF2B6, 0x081C); +#endif /*USE_IQ_IMBAL_SMOOTH*/ + +#ifdef USE_SPUR_CANCEL + if (radio->tc_on) + fmspeedy_set_reg(0xFFF2D3, 0x18); +#endif + + /* Enable the volume control */ + fmspeedy_set_reg_field(0xFFF251, 11, (0x0001 << 11), 1); + + fm_set_band(radio, 0); /*FM band(87.5 ~ 108 MHz)*/ + fm_set_freq_step(radio, 1); /*freq_step(100 KHz)*/ + + fm_set_blend_mute(radio); + fm_set_mute(TRUE); +#ifdef USE_RINGBUFF_API + /* Create the RDS buffer. */ + if (radio->rds_parser_enable) + radio->low->rds_buffer_mem = kzalloc(FM_RDS_MEM_SIZE_PARSER, GFP_KERNEL); + else + radio->low->rds_buffer_mem = kzalloc(FM_RDS_MEM_SIZE, GFP_KERNEL); + + /* ringbuf init */ + if (radio->rds_parser_enable) + radio->rds_rb.size = FM_RDS_MEM_SIZE_PARSER; + else + radio->rds_rb.size = FM_RDS_MEM_SIZE; + + radio->rds_rb.buf = radio->low->rds_buffer_mem; + radio->rds_rb.head = radio->rds_rb.tail= radio->rds_rb.buf; +#else /* USE_RINGBUFF_API */ + /* Create the RDS buffer. */ + radio->low->rds_buffer = (rds_buf_conf *) kzalloc(sizeof(rds_buf_conf), + GFP_KERNEL); + radio->low->rds_buffer_mem = kzalloc(FM_RDS_MEM_SIZE, GFP_KERNEL); + + radio->low->rds_buffer->base = radio->low->rds_buffer_mem; + radio->low->rds_buffer->index = radio->low->rds_buffer->outdex = 0; + radio->low->rds_buffer->size = FM_RDS_MEM_SIZE; +#endif /* USE_RINGBUFF_API */ + + fm_rds_flush_buffers(radio, FALSE); + + API_EXIT(radio); +} + +/**************************************************************************** + + Functions for conversion + + ****************************************************************************/ + +u16 if_count_device_to_host(struct s610_radio *radio, u16 val) +{ + bool negative = !!(val & 0x8000); + u32 resp; + + if (negative) + val = -val; + + resp = ((u32) val) / 128; + + if (resp > 0x7FFF) + resp = 0x7FFF; + + return negative ? (u16) -resp : (u16) resp; +} + +#define AGGR_RSSI_OFFSET (-114) + +static u16 aggr_rssi_host_to_device(u8 val) +{ + s8 val_t = (val > 127) ? ((s16)(val & 0x00FF) - 256) : (s16) val; + u16 resp; + + if (val_t >= AGGR_RSSI_OFFSET) + resp = ((u16) val_t - AGGR_RSSI_OFFSET) * 4; + else + resp = 0; + + return resp; +} + +u8 aggr_rssi_device_to_host(u16 val) +{ + s8 resp; + + resp = (val / 4) + AGGR_RSSI_OFFSET; + return ((u8) resp) & 0x00FF; +} + +u16 rssi_device_to_host(u16 digi_rssi, u16 agc_gain, u16 rssi_adj) +{ + u16 aggr_rssi; + u16 digi_rssi_t = (digi_rssi & 0x1FF); + u16 digi_gain = (agc_gain & 0xF000) >> 12; + u16 ana_gain = (agc_gain & 0x0F80) >> 7; + + aggr_rssi = digi_rssi_t - (12 * digi_gain) - (8 * ana_gain) - rssi_adj + + 160 + 84; + + return aggr_rssi_device_to_host(aggr_rssi); +} + +/**************************************************************************** + + Functions for the interaction with a device + + ****************************************************************************/ +void fm_set_audio_gain(struct s610_radio *radio, u16 gain) +{ + if (gain >= radio->vol_num) + gain = radio->vol_num - 1; + + if (gain < 0) + gain = 0; + + fmspeedy_set_reg_field(0xFFF251, 0, (0x07FF << 0), + radio->vol_level_mod[gain]); +} + +static bool is_freq_in_spur(int freq, u32 *freq_array, int max_freq) { + int i; + + for (i=0; i < max_freq; i++) { + if (freq_array[i] == freq) + return TRUE; + } + return FALSE; +} + +#define AGC_CONFIG_WBRSSI_DISABLE 0x9D1 +#define AGC_CONFIG_WBRSSI_ENABLE 0x95F + +void enable_agc_config_wbrssi(struct s610_radio *radio, bool onoff) +{ + if (onoff) { + /* AGC config WBRSSI_LO/HI enable) */ + fmspeedy_set_reg(0xFFF280, AGC_CONFIG_WBRSSI_ENABLE); + fmspeedy_set_reg(0xFFF29A, 0x0804); + radio->agc_enable = AGC_CONFIG_WBRSSI_ENABLE; + } else { + /* AGC config WBRSSI_LO/HI disable */ + fmspeedy_set_reg(0xFFF280, AGC_CONFIG_WBRSSI_DISABLE); + fmspeedy_set_reg(0xFFF29A, 0x0906); + radio->agc_enable = AGC_CONFIG_WBRSSI_DISABLE; + } + + dev_info(radio->dev, "%s(%d):280:%08X, 29A:%08X ", __func__, + onoff, + fmspeedy_get_reg(0xFFF280), fmspeedy_get_reg(0xFFF29A)); +} + +void reset_agc_gain(void) +{ + /* FM AGC gain reset */ + fmspeedy_set_reg(0xFFF286, 0x33C); + mdelay(5); + fmspeedy_set_reg(0xFFF286, 0x13C); +} + +void fm_set_freq(struct s610_radio *radio, u32 freq, bool mix_hi) +{ + int ii; + u32 fifo_tmp; + + API_ENTRY(radio); + APIEBUG(radio, "set freq: %d", radio->low->fm_state.freq); + + radio->low->fm_tune_info.rx_setup.fm_freq_khz = freq; + radio->low->fm_tune_info.rx_setup.fm_freq_hz = freq * 1000; + + if (mix_hi) { + radio->low->fm_tune_info.lo_setup.rx_lo_req_freq = + radio->low->fm_tune_info.rx_setup.fm_freq_hz + 224609; + radio->low->fm_tune_info.rx_setup.demod_if = 0xF8D; + } else { + radio->low->fm_tune_info.lo_setup.rx_lo_req_freq = + radio->low->fm_tune_info.rx_setup.fm_freq_hz - 224609; + radio->low->fm_tune_info.rx_setup.demod_if = 0x73; + } + + if (radio->dual_clk_on && (radio->rfchip_ver == S620_REV_0)) { + if (is_freq_in_spur(radio->low->fm_state.freq, fm_dual_clk_init, radio->dual_clk_on)) { + if (mix_hi) + radio->low->fm_tune_info.rx_setup.demod_if = 0xF85; + else + radio->low->fm_tune_info.rx_setup.demod_if = 0x7B; + } + } + + fm_lo_prepare_setup(radio); + + if (freq <= 70000) + radio->low->fm_tune_info.rx_setup.lna_cdac = 0x28; + else if (freq < 80000) + radio->low->fm_tune_info.rx_setup.lna_cdac = 0x16; + else if (freq < 90000) + radio->low->fm_tune_info.rx_setup.lna_cdac = 0x12; + else if (freq < 100000) + radio->low->fm_tune_info.rx_setup.lna_cdac = 0x0A; + else + radio->low->fm_tune_info.rx_setup.lna_cdac = 0x05; + + fmspeedy_set_reg_field(0xFFF2A9, 7, (0x0001<<7), 1); + fmspeedy_set_reg_field(0xFFF2A9, 6, (0x0001<<6), 0); + fmspeedy_set_reg_field(0xFFF2A9, 4, (0x0001<<4), 0); + + if (!radio->without_elna) + radio->rssi_adjust = RSSI_ADJUST_WITHOUT_ELNA_VALUE; + + fmspeedy_set_reg(0xFFF2C2, radio->low->fm_config.rssi_adj_ini+radio->rssi_adjust); + APIEBUG(radio, "%s(): 0xFFF2C2: 0x%x %d %d", __func__, + fmspeedy_get_reg(0xFFF2C2), radio->low->fm_config.rssi_adj_ini, radio->rssi_adjust); +#ifdef IDLE_POLLING_ENABLE + fm_idle_periodic_cancel((unsigned long) radio); +#endif /*IDLE_POLLING_ENABLE*/ + + if (radio->trf_on && (radio->rfchip_ver == S620_REV_0)) { + if (is_freq_in_spur(radio->low->fm_state.freq, fm_spur_trf_init, radio->trf_on)) { + fmspeedy_set_reg_field(0xFFF2A9, 7, (0x0001<<7), 0); + fmspeedy_set_reg_field(0xFFF2A9, 6, (0x0001<<6), 1); + fmspeedy_set_reg_field(0xFFF2A9, 4, (0x0001<<4), 1); + if (radio->seek_status == FM_TUNER_PRESET_MODE) + udelay(100); +#ifdef IDLE_POLLING_ENABLE + if (radio->low->fm_state.freq == 104000) + fm_idle_periodic_update((unsigned long) radio); +#endif /*IDLE_POLLING_ENABLE*/ + dev_info(radio->dev, "TRF ON [%06d][%08X]", + radio->low->fm_state.freq, fmspeedy_get_reg(0xFFF2A9)); + } + } + +#ifdef USE_SPUR_CANCEL + if (radio->tc_on) + fm_rx_check_spur(radio); +#endif + +#ifdef USE_IQ_IMBAL_SMOOTH + /* Clear the smooth config lock for IQ imbalance */ + fmspeedy_set_reg_field(0xFFF2B6, 10, (0x0001 << 10), 0); +#endif /*USE_IQ_IMBAL_SMOOTH*/ + + /* Set up CDAC */ + fmspeedy_set_reg_field(0xFFF264, 22, (0x003F << 22), + radio->low->fm_tune_info.rx_setup.lna_cdac); + + if (freq == 104000) { + fmspeedy_set_reg(0xFFF244, 0x0A20D0); + fmspeedy_set_reg_field(0xFFF265, 17, (0x0001<<17), 1); /* AUX_SEL_RX_ADC_CLK_30M */ + fmspeedy_set_reg_field(0xFFF241, 9, (0x0001<<9), 0); /* LO_CLKREF_ADC_SEL_CLKREF */ + fmspeedy_set_reg_field(0xFFF258, 27, (0x0001<<27), 0); /* XTAL_EN_FM_BUF off*/ + } else { + fmspeedy_set_reg(0xFFF244, 0x24A0D0); + fmspeedy_set_reg_field(0xFFF265, 17, (0x0001<<17), 0); /* AUX_SEL_RX_ADC_CLK_30M */ + fmspeedy_set_reg_field(0xFFF241, 9, (0x0001<<9), 1); /* LO_CLKREF_ADC_SEL_CLKREF */ + fmspeedy_set_reg_field(0xFFF258, 27, (0x0001<<27), 1); /* XTAL_EN_FM_BUF on */ + } + + /* Set up LO */ + fm_lo_set(radio->low->fm_tune_info.lo_setup); + + if (!radio->dual_clk_on) { + if ((freq == 99900) || (freq == 100000) || (freq == 100100)) { + fmspeedy_set_reg_field(0xFFF255, 21, (0x0001<<21), 1); /* FMCLK_32M */ + fmspeedy_set_reg_field(0xFFF2A8, 5, (0x0001 << 5), 1); + } else { + fmspeedy_set_reg_field(0xFFF255, 21, (0x0001<<21), 0); /* FMCLK_40M */ + fmspeedy_set_reg_field(0xFFF2A8, 5, (0x0001 << 5), 0); + } + } + +#if 0 + /* Initialise I/Q imbalance */ + fm_setup_iq_imbalance(); +#endif + + fmspeedy_set_reg_field(0xFFF255, 21, (0x0001<<21), 0); /* FMCLK_40_32M, default = 1 */ + /* Set up Demod IF */ + fmspeedy_set_reg_field(0xFFF2A8, 7, (0x0001<<7), 0); + fmspeedy_set_reg_field(0xFFF2A8, 8, (0x0001<<8), 0); + if (radio->seek_status == FM_TUNER_PRESET_MODE) + udelay(100); + fmspeedy_set_reg_field(0xFFF222, 0, (0x000F), 0x02); + fmspeedy_set_reg_field(0xFFF222, 4, (0x000F<<4), 0x09); + if (radio->seek_status == FM_TUNER_PRESET_MODE) + udelay(100); + + if (radio->dual_clk_on && (radio->rfchip_ver == S620_REV_0)) { + if (is_freq_in_spur(radio->low->fm_state.freq, fm_dual_clk_init, radio->dual_clk_on)) { + fmspeedy_set_reg_field(0xFFF255, 21, (0x0001<<21), 1); /* FMCLK_40_32M, default = 1 */ + /* Set up Demod IF */ + fmspeedy_set_reg_field(0xFFF2A8, 7, (0x0001<<7), 1); + fmspeedy_set_reg_field(0xFFF2A8, 8, (0x0001<<8), 1); + if (radio->seek_status == FM_TUNER_PRESET_MODE) + udelay(100); + fmspeedy_set_reg_field(0xFFF222, 0, (0x000F), 0x03); + fmspeedy_set_reg_field(0xFFF222, 4, (0x000F<<4), 0x07); + if (radio->seek_status == FM_TUNER_PRESET_MODE) + udelay(100); + dev_info(radio->dev, "7.5MHz Dual Clock ON [%06d]", radio->low->fm_state.freq); + } + } + + fmspeedy_set_reg(0xFFF2AF, radio->low->fm_tune_info.rx_setup.demod_if); + fmspeedy_set_reg(0xFFF2AE, 0x924); /* wide w te */ + + if (radio->rssi_est_on) { + fm_update_rssi(radio); + if (radio->low->fm_state.rssi >= 176) + fmspeedy_set_reg_field(0xFFF2A9, 4, (0x0001 << 4), 1); + } else + fmspeedy_set_reg_field(0xFFF2A9, 4, (0x0001 << 4), 0); + + if (radio->sw_mute_weak) { + radio->low->fm_config.mute_coeffs_soft = 0x1B16; + fmspeedy_set_reg(0xFFF2CA, radio->low->fm_config.mute_coeffs_soft); + } + + /* change blending ref to RSSI */ + if (radio->low->fm_state.freq != 104000) { + if(radio->rssi_ref_enable) + fmspeedy_set_reg_field(0xFFF2A9, 4, (0x0001 << 4), 1); + else + fmspeedy_set_reg_field(0xFFF2A9, 4, (0x0001 << 4), 0); + } + + /* FM ADC reset */ + fmspeedy_set_reg_field(0xFFF304, 5, (0x0001<<5), 0); + fmspeedy_set_reg_field(0xFFF304, 5, (0x0001<<5), 1); + + FDEBUG(radio, "%s():seek_status:%d %d", __func__, + radio->seek_status, radio->low->fm_state.tuner_mode); + + /* FM AGC config control */ + if (radio->seek_status == FM_TUNER_PRESET_MODE) { + enable_agc_config_wbrssi(radio, TRUE); + reset_agc_gain(); + } else { + if (radio->agc_enable != AGC_CONFIG_WBRSSI_DISABLE) + enable_agc_config_wbrssi(radio, FALSE); + } + + if (radio->rds_parser_enable) { + /* RDS parser reset */ + fm_rds_parser_reset(&(radio->pi)); + + /* FIFO clear */ + for (ii = 0; ii < 32; ii++) + fifo_tmp = fmspeedy_get_reg_work(0xFFF3C0); + +#ifdef USE_RINGBUFF_API + radio->rds_rb.head = radio->rds_rb.tail= radio->rds_rb.buf; +#else /* USE_RINGBUFF_API */ + radio->low->rds_buffer->index = radio->low->rds_buffer->outdex = 0; +#endif /* USE_RINGBUFF_API */ + } + + API_EXIT(radio); +} + +void fm_set_mute(bool mute) +{ + if (mute) + fmspeedy_set_reg_field(0xFFF2A9, 0, 0x0001, 1); /* mute*/ + else + fmspeedy_set_reg_field(0xFFF2A9, 0, 0x0001, 0); /*unmute*/ +} + +void fm_set_blend_mute(struct s610_radio *radio) +{ + u16 mute_coeffs, blend_coeffs; + +#ifdef MONO_SWITCH_INTERF + if ((radio->low->fm_state.force_mono) + || (radio->low->fm_state.force_mono_interf)) { +#else + if (radio->low->fm_state.force_mono) { +#endif + blend_coeffs = radio->low->fm_config.blend_coeffs_dis; + } else if (radio->low->fm_state.use_switched_blend) { + /* Switched blend mode */ + blend_coeffs = radio->low->fm_config.blend_coeffs_switch; + } else { + /* Soft blend mode */ + blend_coeffs = radio->low->fm_config.blend_coeffs_soft; + } + + if (radio->low->fm_state.use_soft_mute) { + /* Soft mute */ + mute_coeffs = radio->low->fm_config.mute_coeffs_soft; + } else { + mute_coeffs = radio->low->fm_config.mute_coeffs_dis; + } + + fmspeedy_set_reg(0xFFF2CC, blend_coeffs); + fmspeedy_set_reg(0xFFF2CA, mute_coeffs); +} + +static void fm_rds_flush_buffers(struct s610_radio *radio, bool clear_buffer) +{ + bool clear_sync = FALSE; + +#ifdef USE_RINGBUFF_API + /* Clear the buffer pointers. */ + radio->rds_rb.head = radio->rds_rb.tail= radio->rds_rb.buf; +#else /* USE_RINGBUFF_API */ + if (radio->low->rds_buffer != 0) + /* Clear the buffer pointers. */ + radio->low->rds_buffer->index = + radio->low->rds_buffer->outdex = 0; +#endif /* USE_RINGBUFF_API */ + + if (clear_buffer) { + /* Diable RDS block */ + fmspeedy_set_reg_field(0xFFF304, 1, (0x0001 << 1), 0); +#ifndef RDS_POLLING_ENABLE + /* Disable RDS int. */ + fm_set_interrupt_source((0x0001 << 4), FALSE); +#endif /* RDS_POLLING_ENABLE */ + /* Initialize the RDS state */ + radio->low->fm_rds_state.current_state = RDS_STATE_INIT; + /* Clear the Sync flag after updating the status */ + clear_sync = TRUE; + /* Enable RDS block */ + fmspeedy_set_reg_field(0xFFF304, 1, (0x0001 << 1), 1); +#ifndef RDS_POLLING_ENABLE + /* Enable RDS int. */ + fm_set_interrupt_source((0x0001 << 4), TRUE); +#endif /* RDS_POLLING_ENABLE */ + } + + fm_clear_flag_bits(radio, FLAG_BUF_FUL); + radio->low->fm_state.status &= ~STATUS_MASK_RDS_AVA; + + if (clear_sync) + fm_update_rds_sync_status(radio, FALSE); +} + +bool fm_radio_on(struct s610_radio *radio) +{ + u32 fm_en; + + API_ENTRY(radio); + + /* Start up analogue block */ + fm_rx_ana_start(); + + /* Enable FM, DEMOD and ADC. */ + fm_en = fmspeedy_get_reg(0xFFF304); + fmspeedy_set_reg(0xFFF304, (fm_en & 0x1DA)); + fmspeedy_set_reg(0xFFF304, (fm_en | 0x25)); + + /* Clear int source */ + fmspeedy_set_reg(0xFFF302, 0xFFFF); + + radio->low->fm_state.last_status_blend_stereo = FALSE; + radio->low->fm_state.last_status_rds_sync = FALSE; + + API_EXIT(radio); + /* Indicate success */ + return TRUE; +} + +void fm_radio_off(struct s610_radio *radio) +{ + /* Disable all interrupt. */ + fm_set_interrupt_source(0xFFFF, FALSE); + + /* disable AudioOutEn */ + fm_audio_control(radio, 0, 0, 0, 0); + + /* Turn off FM digital block */ + fmspeedy_set_reg(0xFFF304, 0); + + /* Turn off analogue block */ + fm_rx_ana_stop(); +} + +void fm_rds_on(struct s610_radio *radio) +{ + memset(&radio->low->fm_rds_state, 0, sizeof(radio->low->fm_rds_state)); + + /* Set the interrupt rate for RDS */ + fmspeedy_set_reg(0xFFF2BF, radio->low->fm_config.rds_int_byte_count); +} + +void fm_rds_off(struct s610_radio *radio) +{ + radio->low->fm_state.status &= ~STATUS_MASK_RDS_AVA; +} + +void fm_rds_enable(struct s610_radio *radio) +{ +#ifndef USE_RDS_HW_DECODER + u32 val = fmspeedy_get_reg(0xFFF2D7); + u32 mask = ~0x2800; + + val &= mask; + fmspeedy_set_reg(0xFFF2D7, val | 0x800); +#endif /*USE_RDS_HW_DECODER*/ + fm_rds_flush_buffers(radio, TRUE); +} + +void fm_rds_disable(struct s610_radio *radio) +{ + /* Diable RDS block */ + fmspeedy_set_reg_field(0xFFF304, 1, (0x0001 << 1), 0); + /* Disable RDS int. */ + fm_set_interrupt_source((0x0001 << 4), FALSE); + /* Clear RDS sync. */ + fm_update_rds_sync_status(radio, FALSE); +} + +/**************************************************************************** + + Functions for the information management + + ****************************************************************************/ + +u16 fm_get_flags(struct s610_radio *radio) +{ + u16 resp = radio->low->fm_state.flags; + + fm_set_flags(radio, 0); + + return resp; +} + +void fm_set_flags(struct s610_radio *radio, u16 flags) +{ + radio->low->fm_state.flags = flags; +} + +void fm_update_if_count(struct s610_radio *radio) +{ + radio->low->fm_state.last_ifc = if_count_device_to_host(radio, + fmspeedy_get_reg(0xFFF2B0)); +} + +void fm_update_if_count_int(struct s610_radio *radio) +{ + radio->low->fm_state.last_ifc = if_count_device_to_host(radio, + fmspeedy_get_reg(0xFFF2B0)); +} + +void fm_update_rssi(struct s610_radio *radio) +{ + radio->low->fm_state.rssi = + rssi_device_to_host(fmspeedy_get_reg(0xFFF2AD), + fmspeedy_get_reg(0xFFF285), fmspeedy_get_reg(0xFFF2C2)); +} + +void fm_update_rssi_work(struct s610_radio *radio) +{ + radio->low->fm_state.rssi = + rssi_device_to_host(fmspeedy_get_reg_work(0xFFF2AD), + fmspeedy_get_reg_work(0xFFF285), fmspeedy_get_reg_work(0xFFF2C2)); +} + +void fm_update_snr(struct s610_radio *radio) +{ + radio->low->fm_state.snr = fmspeedy_get_reg(0xFFF2C5); +} + +void fm_update_sig_info(struct s610_radio *radio) +{ + fm_update_rssi(radio); + fm_update_snr(radio); +} + +void fm_update_rds_sync_status(struct s610_radio *radio, bool synced) +{ + if (radio->low->fm_state.last_status_rds_sync != synced) { + if (synced != TRUE) + fm_set_flag_bits(radio, FLAG_SYN_LOS); + + radio->low->fm_state.last_status_rds_sync = synced; + } +} + +#ifndef USE_RDS_HW_DECODER +bool fm_get_rds_sync_status(struct s610_radio *radio) +{ + return radio->low->fm_state.last_status_rds_sync; +} +#endif /*USE_RDS_HW_DECODER*/ + +u16 fm_update_rx_status(struct s610_radio *radio, u16 d_status) +{ + u16 flags = 0; + u8 status = radio->low->fm_state.status & ~STATUS_MASK_STEREO; + bool blend_stereo = !!(d_status & FM_DEMOD_BLEND_STEREO_MASK); + + if (blend_stereo + != radio->low->fm_state.last_status_blend_stereo) { + radio->low->fm_state.last_status_blend_stereo = blend_stereo; + flags |= FLAG_CH_STAT; + } + if (blend_stereo) + status |= STATUS_MASK_STEREO; + + radio->low->fm_state.status = status; + + return flags; +} + +void fm_update_tuner_mode(struct s610_radio *radio) +{ + u8 tuner_mode = radio->low->fm_state.tuner_mode + & ~TUNER_MODE_MASK_TUN_MOD; + u32 tuner_state = (u32) radio->low->fm_tuner_state.tuner_state; + + switch (tuner_state) { + case TUNER_OFF: + tuner_mode |= TUNER_MODE_NONE; + break; + case TUNER_NOTTUNED: + tuner_mode |= TUNER_MODE_NONE; + break; + case TUNER_IDLE: + tuner_mode |= TUNER_MODE_NONE; + break; + case TUNER_PRESET: + tuner_mode |= TUNER_MODE_PRESET; + break; + case TUNER_SEARCH: + tuner_mode |= TUNER_MODE_SEARCH; + break; + default: + break; + } + + radio->low->fm_state.tuner_mode = tuner_mode; +} + +bool fm_check_rssi_level(u16 limit) +{ + u16 d_rssi, gain, adjust; + s16 rssi, thres; + + d_rssi = fmspeedy_get_reg(0xFFF2AD); + gain = fmspeedy_get_reg(0xFFF285); + adjust = fmspeedy_get_reg(0xFFF2C2); + + rssi = rssi_device_to_host(d_rssi, gain, adjust); + thres = aggr_rssi_device_to_host(limit); + + rssi = (rssi & 0x80) ? rssi - 256 : rssi; + thres = (thres & 0x80) ? thres - 256 : thres; + + return (rssi < thres); +} + +/*******************************************************************/ +int low_get_search_lvl(struct s610_radio *radio, u16 *value) +{ + *value = + aggr_rssi_device_to_host( + radio->low->fm_state.rssi_limit_search); + + return 0; +} +/*******************************************************************/ +/* set function */ +int low_set_if_limit(struct s610_radio *radio, u16 value) +{ + fmspeedy_set_reg(0xFFF2B3, (u8) value); + + return 0; +} + +int low_set_search_lvl(struct s610_radio *radio, u16 value) +{ + radio->low->fm_state.rssi_limit_search = + aggr_rssi_host_to_device(value); + fm_set_rssi_thresh(radio, radio->low->fm_tuner_state.tuner_state); + + return 0; +} + +int low_set_freq(struct s610_radio *radio, u32 value) +{ + u32 freq = value; + + (void) fm_band_trim(radio, &freq); + radio->low->fm_state.freq = freq; + + return 0; +} + +int low_set_tuner_mode(struct s610_radio *radio, u16 value) +{ + API_ENTRY(radio); + + radio->low->fm_state.tuner_mode = value; + radio->low->fm_tuner_state.curr_search_down = + radio->low->fm_state.search_down; + + fm_set_tuner_mode(radio); + + radio->seek_status = value; + FDEBUG(radio, "%s(), seek_status:%d %d", __func__, radio->seek_status, value); + + /* FM AGC config control */ + if (radio->seek_status == FM_TUNER_PRESET_MODE) { + enable_agc_config_wbrssi(radio, TRUE); + reset_agc_gain(); + } + + if (value == FM_TUNER_STOP_SEARCH_MODE) { + /* Seek_cacel complete */ + complete(&radio->flags_seek_fr_comp); + dev_info(radio->dev, ">>> send seek cancel complete"); + } + + API_EXIT(radio); + + return 0; +} + +void fm_tuner_set_force_mute(struct s610_radio *radio, bool mute) +{ + radio->low->fm_state.mute_forced = mute; + radio->low->fm_state.mute_audio = mute; + fm_tuner_control_mute(radio); +} + +int low_set_mute_state(struct s610_radio *radio, u16 value) +{ + /* Default set only fm stat initialie */ + /*radio->low->fm_state.use_soft_mute = !!(value & MUTE_STATE_MASK_SOFT);*/ + + fm_tuner_set_force_mute(radio, !!(value & MUTE_STATE_MASK_HARD)); + fm_set_blend_mute(radio); + + return 0; +} + +int low_set_most_mode(struct s610_radio *radio, u16 value) +{ + radio->low->fm_state.force_mono = !(value & MODE_MASK_MONO_STEREO); + fm_set_blend_mute(radio); + + return 0; +} + +int low_set_most_blend(struct s610_radio *radio, u16 value) +{ +/* radio->low->fm_state.use_switched_blend = !!(value & MODE_MASK_BLEND);*/ + fm_set_blend_mute(radio); + + return 0; +} + +int low_set_pause_lvl(struct s610_radio *radio, u16 value) +{ + fmspeedy_set_reg(0xFFF2A4, (u8)(value & 0x00FF)); + + return 0; +} + +int low_set_pause_dur(struct s610_radio *radio, u16 value) +{ + fmspeedy_set_reg(0xFFF2A2, (u8)(value & 0x3F)); + + return 0; +} + +int low_set_demph_mode(struct s610_radio *radio, u16 value) +{ + if (value & MODE_MASK_DEEMPH) + fmspeedy_set_reg_field(0xFFF2A9, 1, (0x0001 << 1), 1); + else + fmspeedy_set_reg_field(0xFFF2A9, 1, (0x0001 << 1), 0); + + return 0; +} + +int low_set_rds_cntr(struct s610_radio *radio, u16 value) +{ + if (value & RDS_CTRL_MASK_FLUSH) + fm_rds_flush_buffers(radio, !!(value & RDS_CTRL_MASK_RESYNC)); + + return 0; +} + +int low_set_power(struct s610_radio *radio, u16 value) +{ + fm_tuner_set_power_state(radio, + value & PWR_MASK_FM, value & PWR_MASK_RDS); + + return 0; +} + + +/**************************************************************************** + + Functions for interrupt + + ****************************************************************************/ +#ifndef RDS_POLLING_ENABLE +void fm_set_handler_if_count(void (*fn)(struct s610_radio *radio)) +{ + handler_if_count = fn; + fm_set_interrupt_source(1, fn ? TRUE : FALSE); +} + +void fm_set_handler_audio_pause(void (*fn)(struct s610_radio *radio)) +{ + handler_audio_pause = fn; + fm_set_interrupt_source((1 << 3), fn ? TRUE : FALSE); +} +#endif /*RDS_POLLING_ENABLE*/ + +void fm_set_interrupt_source(u16 sources, bool enable) +{ + u32 mask; + + if (enable) { + /*fmspeedy_set_reg(0xFFF302, sources);*//* clear int. */ + /* Get int. mask */ + mask = fmspeedy_get_reg(0xFFF303); + /* Set Int. mask */ + fmspeedy_set_reg(0xFFF303, (mask | sources)); + } else { + /* Get int. mask */ + mask = fmspeedy_get_reg(0xFFF303); + /* Set Int. mask */ + fmspeedy_set_reg(0xFFF303, (mask & ~sources)); + } +} + +#ifdef ENABLE_RDS_WORK_QUEUE +void s610_rds_work(struct work_struct *work) +{ + struct s610_radio *radio; + + radio = container_of(work, struct s610_radio, work); + + /* FDEBUG(radio, ">R");*/ + fm_process_rds_data(radio); +} +#endif /*ENABLE_RDS_WORK_QUEUE*/ + +#ifndef RDS_POLLING_ENABLE +#ifdef ENABLE_IF_WORK_QUEUE +void s610_if_work(struct work_struct *work) +{ + struct s610_radio *radio; + + radio = container_of(work, struct s610_radio, if_work); + + /* FUNC_ENTRY(radio);*/ + FDEBUG(radio, ">IF"); + + if (handler_if_count) + (*handler_if_count)(radio); + + /* FUNC_EXIT(radio);*/ +} +#endif /*ENABLE_IF_WORK_QUEUE*/ +#endif /* RDS_POLLING_ENABLE */ + +void s610_sig2_work(struct work_struct *work) +{ + struct s610_radio *radio; + + radio = container_of(work, struct s610_radio, dwork_sig2.work); + + FDEBUG(radio, ">S;%d, %d", radio->low->fm_config.search_conf.normal_ifca_m, + radio->low->fm_config.search_conf.normal_ifca_h); + fm_search_check_signal2((unsigned long) radio); +} + +void s610_tune_work(struct work_struct *work) +{ + struct s610_radio *radio; + + radio = container_of(work, struct s610_radio, dwork_tune.work); + + FDEBUG(radio, ">T"); + fm_search_tuned((unsigned long) radio); +} + +#ifdef RDS_POLLING_ENABLE +#define RDS_MAX_FIFO 32 +void s610_rds_poll_work(struct work_struct *work) +{ + struct s610_radio *radio; + u32 fifo_status, rds_count; + + radio = container_of(work, struct s610_radio, dwork_rds_poll.work); + + if (radio->rds_flag == FM_RDS_ENABLE) { + spin_lock_irq(&radio->slock); + + fm_update_rssi_work(radio); + if (radio->low->fm_state.rssi < RDS_VALID_THRESHOLD) { + RDSEBUG(radio, "RDS Current RSSI invalid!![%02d][%02d]", + radio->low->fm_state.rssi, RDS_VALID_THRESHOLD); + radio->invalid_rssi = TRUE; + goto fm_periodic_update; + } + + fifo_status = fmspeedy_get_reg_work(0xFFF398); + if (fifo_status & 0x02) { + dev_info(radio->dev, ">>>>> RDS FIFO FULL restart RDS!!"); + } + + rds_count = (fifo_status >> 8) & 0x3F; + if (rds_count >= RDS_MAX_FIFO/4) { + RDSEBUG(radio,"%s(): rds_count:%d fifo_status[%08X]", + __func__, rds_count, fifo_status); + fm_process_rds_data(radio); + } + +fm_periodic_update: + fm_rds_periodic_update((unsigned long) radio); + + spin_unlock_irq(&radio->slock); + } +} +#endif /*RDS_POLLING_ENABLE*/ + +#ifdef IDLE_POLLING_ENABLE +void s610_idle_poll_work(struct work_struct *work) +{ + struct s610_radio *radio; + int rssi; + + radio = container_of(work, struct s610_radio, dwork_idle_poll.work); + + if (!wake_lock_active(&radio->wakelock)) + wake_lock(&radio->wakelock); + + spin_lock_irq(&radio->slock); + + fm_update_rssi_work(radio); + rssi = radio->low->fm_state.rssi; + rssi = (rssi & 0x80) ? rssi - 256 : rssi; + APIEBUG(radio, "Current RSSI is [%02d][%02d] TRF ON/OFF RSSI[%2d:%2d] SNR ON/FF RSSI[%2d:%2d]", + rssi, radio->low->fm_state.rssi, + TRF_ON_RSSI_VALUE, TRF_OFF_RSSI_VALUE, + SNR_ON_RSSI_VALUE, SNR_OFF_RSSI_VALUE); + + if (radio->low->fm_state.freq == 104000) { + /* TRF ON ? */ + if ((!fmspeedy_get_reg_field_work(0xFFF2A9, 7, (0x0001<<7))) && + (fmspeedy_get_reg_field_work(0xFFF2A9, 6, (0x0001<<6)))) { + if ((radio->low->fm_state.rssi >= TRF_OFF_RSSI_VALUE)) { + if (!fmspeedy_get_reg_field_work(0xFFF2A9, 7, (0x0001<<7))) + fmspeedy_set_reg_field_work(0xFFF2A9, 7, (0x0001<<7), 1); + + if (fmspeedy_get_reg_field_work(0xFFF2A9, 6, (0x0001<<6))) + fmspeedy_set_reg_field_work(0xFFF2A9, 6, (0x0001<<6), 0); + } + } else { /* TRF OFF? */ + if ((fmspeedy_get_reg_field_work(0xFFF2A9, 7, (0x0001<<7))) && + (!fmspeedy_get_reg_field_work(0xFFF2A9, 6, (0x0001<<6)))) { + if ((radio->low->fm_state.rssi < TRF_ON_RSSI_VALUE)) { + if (fmspeedy_get_reg_field_work(0xFFF2A9, 7, (0x0001<<7))) + fmspeedy_set_reg_field_work(0xFFF2A9, 7, (0x0001<<7), 0); + if (!fmspeedy_get_reg_field_work(0xFFF2A9, 6, (0x0001<<6))) + fmspeedy_set_reg_field_work(0xFFF2A9, 6, (0x0001<<6), 1); + } + } + } + + /* SNR deviation bit 4 : 0 ? */ + if (!fmspeedy_get_reg_field_work(0xFFF2A9, 4, (0x0001 << 4))) { + if (radio->low->fm_state.rssi >= SNR_OFF_RSSI_VALUE) { + fmspeedy_set_reg_field_work(0xFFF2A9, 4, (0x0001 << 4), 1); + } + } else { + /* SNR deviation bit 4 : 1 ? */ + if (radio->low->fm_state.rssi < SNR_ON_RSSI_VALUE) { + fmspeedy_set_reg_field_work(0xFFF2A9, 4, (0x0001 << 4), 0); + } + } + } + + APIEBUG(radio, ">>> 0xFFF2A9 bit[7:6:4] and 0xFFF2C2 is [%02d:%02d:%02d][%04x]", + fmspeedy_get_reg_field_work(0xFFF2A9, 7, (0x0001<<7)), + fmspeedy_get_reg_field_work(0xFFF2A9, 6, (0x0001<<6)), + fmspeedy_get_reg_field_work(0xFFF2A9, 4, (0x0001<<4)), + fmspeedy_get_reg_work(0xFFF2C2)); + + fm_idle_periodic_update((unsigned long) radio); + + spin_unlock_irq(&radio->slock); + + if (wake_lock_active(&radio->rdswakelock)) + wake_unlock(&radio->rdswakelock); + +} +#endif /*IDLE_POLLING_ENABLE*/ + +#ifndef RDS_POLLING_ENABLE +void fm_isr(struct s610_radio *radio) +{ + u16 cause; + + cause = fmspeedy_get_reg(0xFFF301); /* save */ + + fmspeedy_set_reg(0xFFF302, cause); /* clear */ + udelay(10); + + cause &= fmspeedy_get_reg(0xFFF303); /* mask */ + + if (cause & INT_IFC_READY_MASK) { + fmspeedy_set_reg(0xFFF303, + fmspeedy_get_reg(0xFFF303) & 0xFFFE); +#ifdef ENABLE_IF_WORK_QUEUE + schedule_work(&radio->if_work); +#else + if (handler_if_count) + (*handler_if_count)(radio); + +#endif /*ENABLE_IF_WORK_QUEUE*/ + + } + + if (cause & INT_RDS_BYTES_MASK) { +#ifdef ENABLE_RDS_WORK_QUEUE + schedule_work(&radio->work); +#else + fm_process_rds_data(radio); +#endif /*ENABLE_RDS_WORK_QUEUE*/ + } + + if (cause & INT_AUDIO_PAU_MASK) { + fmspeedy_set_reg(0xFFF303, + fmspeedy_get_reg(0xFFF303) & 0xFFF7); + + if (handler_audio_pause) + (*handler_audio_pause)(radio); + } +} +#endif /* RDS_POLLING_ENABLE */ + +/**************************************************************************** + + Functions for RX + + ****************************************************************************/ + +void fm_rx_ana_start(void) +{ + u32 adc_config1 = 0; + + /* ADC setting */ + adc_config1 = 0x01EF7A53; + + /* RF setting */ + fmspeedy_set_reg(0xFFF263, 0xFC1CDFFF); + /* fmspeedy_set_reg(0xFFF265, 0x80988002); */ + fmspeedy_set_reg(0xFFF265, 0x81788002); + fmspeedy_set_reg(0xFFF264, 0x040003FD); + + /* ADC input disconnect */ + fmspeedy_set_reg(0xFFF261, 0); + /* ADC enable */ + fmspeedy_set_reg(0xFFF260, adc_config1); + /* ADC reset */ + fmspeedy_set_reg(0xFFF260, adc_config1 | (1 << 31) | (1 << 30)); + fmspeedy_set_reg(0xFFF260, adc_config1); + /* Overload block reset */ + fmspeedy_set_reg(0xFFF260, adc_config1 | (1 << 25)); + fmspeedy_set_reg(0xFFF260, adc_config1); + /* ADC input connect */ + fmspeedy_set_reg(0xFFF261, 2); +} + +void fm_rx_ana_stop(void) +{ + fmspeedy_set_reg(0xFFF260, 0); + fmspeedy_set_reg(0xFFF261, 0); + fmspeedy_set_reg(0xFFF263, 0); + fmspeedy_set_reg(0xFFF264, 0); + fmspeedy_set_reg(0xFFF265, 0); +} + +void fm_setup_iq_imbalance(void) +{ + fmspeedy_set_reg_field(0xFFF2B4, 0, 0x03FF, 511); + fmspeedy_set_reg_field(0xFFF2B5, 0, 0x03FF, 511); +} + +void fm_rx_init(void) +{ + /* Turn off analogue. */ + fm_rx_ana_stop(); +} + +#ifdef USE_SPUR_CANCEL +void fm_rx_en_spur_removal(struct s610_radio *radio) +{ + fmspeedy_set_reg_field(0xFFF2A9, 13, (0x0001 << 13), 0); + fmspeedy_set_reg_field(0xFFF2A9, 12, (0x0001 << 12), 1); + fmspeedy_set_reg(0xFFF2D2, radio->low->fm_tune_info.rx_setup.spur_freq); +} + +void fm_rx_dis_spur_removal(void) +{ + fmspeedy_set_reg_field(0xFFF2A9, 13, (0x0001 << 13), 1); + fmspeedy_set_reg_field(0xFFF2A9, 12, (0x0001 << 12), 0); +} + +void fm_rx_check_spur(struct s610_radio *radio) +{ + u32 freq_gap_khz; + u16 i; + + fm_rx_dis_spur_removal(); + + for (i = 0; i < radio->tc_on; i++) { + if (radio->low->fm_tune_info.rx_setup.fm_freq_khz + >= radio->low->fm_spur[i]) { + if ((radio->low->fm_tune_info.rx_setup.fm_freq_khz + - radio->low->fm_spur[i]) < 160) { + freq_gap_khz = + radio->low->fm_tune_info.rx_setup.fm_freq_khz + - radio->low->fm_spur[i]; + + radio->low->fm_tune_info.rx_setup.spur_freq = + (s16)((freq_gap_khz * 2048) / 10); + fm_rx_en_spur_removal(radio); + break; + } + } else { + if ((radio->low->fm_spur[i] + - radio->low->fm_tune_info.rx_setup.fm_freq_khz) < 160) { + freq_gap_khz = + radio->low->fm_spur[i] + - radio->low->fm_tune_info.rx_setup.fm_freq_khz; + + radio->low->fm_tune_info.rx_setup.spur_freq = + (s16)(((freq_gap_khz * 2048) / 10) * (-1)); + fm_rx_en_spur_removal(radio); + break; + } + } + + } +} + +void fm_rx_check_spur_mono(struct s610_radio *radio) +{ + if ((radio->low->fm_tune_info.rx_setup.spur_ctrl & + DIS_SPUR_REMOVAL_MONO) && + (!fmspeedy_get_reg_field(0xFFF2CB, 1, 0x0001 << 1))) { + fm_rx_dis_spur_removal(); + + /* Disable spur removal */ + radio->low->fm_tune_info.rx_setup.spur_ctrl = 0; + } +} +#endif + +/**************************************************************************** + + Functions for LO + + ****************************************************************************/ + +void fm_lo_off(void) +{ + fmspeedy_set_reg(0xFFF240, 0); +} + +void fm_lo_prepare_setup(struct s610_radio *radio) +{ + u32 freq_hz; + u32 fref; + u32 flimit; + s64 flodiv_prev, flodiv_cur; + u16 ii; + u32 n_lodiv; + + u64 fdco_t, ndiv_t, fcw_total = 0; + u64 fdco_r; + + freq_hz = radio->low->fm_tune_info.lo_setup.rx_lo_req_freq; + if (radio->low->fm_tune_info.rx_setup.fm_freq_khz == 104000) + fref = 20000000; + else + fref = 26000000; + + flimit = 3000000000; + + /* Calculate the division value of LO divider + Look for the index of the smallest abs value */ + for (ii = 1; ii < 11; ii++) { + flodiv_cur = ABS((int)(flimit - ((ii + 13) * freq_hz * 2))); + if (ii == 1) + flodiv_prev = flodiv_cur; + + n_lodiv = ii + 13; + if (flodiv_cur > flodiv_prev) { + n_lodiv -= 1; + break; + } + flodiv_prev = flodiv_cur; + } + + fdco_t = freq_hz * n_lodiv * 2; + ndiv_t = fdco_t / (u64) fref; + fdco_r = fdco_t % (u64) fref; + + ndiv_t *= (1 << 22); + fdco_r = ((fdco_r * (1 << 22)) + (fref >> 1)) / (u64) fref; + + fcw_total = ndiv_t + fdco_r; + + radio->low->fm_tune_info.lo_setup.n_mmdiv = (u32)( + (fcw_total >> 22) & 0x1FF); + radio->low->fm_tune_info.lo_setup.frac_b1 = (u32)( + ((fcw_total % (1 << 22)) >> 10) & 0xFFF); + radio->low->fm_tune_info.lo_setup.frac_b0 = (u32)( + (fcw_total % (1 << 10)) & 0x3FF); + radio->low->fm_tune_info.lo_setup.n_lodiv = n_lodiv; + +} + +void fm_lo_set(const struct_fm_lo_setup lo_set) +{ + fmspeedy_set_reg(0xFFF242, + (1 << 21) | (lo_set.n_mmdiv << 12) | lo_set.frac_b1); + fmspeedy_set_reg(0xFFF243, + (1 << 21) + | (lo_set.frac_b0 << 11) + | (lo_set.n_lodiv << 6) + | 8); + udelay(100); +} + +void fm_lo_initialize(struct s610_radio *radio) +{ + API_ENTRY(gradio); + + fm_sx_reset(); + + /* Set up the default PLL frequency */ + radio->low->fm_tune_info.lo_setup.rx_lo_req_freq = 76000000; + + /* Turn on the logic controller and dividers. */ + fm_sx_start(); + + API_EXIT(gradio); +} + +void fm_sx_reset(void) +{ + API_ENTRY(gradio); + + /* Reset the FM_SX registers */ + if (gradio->rfchip_ver == S620_REV_0) { + fmspeedy_set_reg(0xFFF240, 0x00302A); + fmspeedy_set_reg(0xFFF241, 0x004600); + fmspeedy_set_reg(0xFFF242, 0x27365E); + fmspeedy_set_reg(0xFFF243, 0x10BDC8); + fmspeedy_set_reg(0xFFF244, 0x0A20D0); + fmspeedy_set_reg(0xFFF245, 0x018132); + fmspeedy_set_reg(0xFFF246, 0x065A78); + fmspeedy_set_reg(0xFFF247, 0x243100); + fmspeedy_set_reg(0xFFF248, 0x0C0518); + fmspeedy_set_reg(0xFFF249, 0); + fmspeedy_set_reg(0xFFF24B, 0); + fmspeedy_set_reg(0xFFF24C, 0x01F8F4); + fmspeedy_set_reg(0xFFF24D, 0); + fmspeedy_set_reg(0xFFF24E, 0); + fmspeedy_set_reg(0xFFF24F, 0x26081D); + fmspeedy_set_reg(0xFFF250, 0); + fmspeedy_set_reg(0xFFF251, 0x2C0000); + fmspeedy_set_reg(0xFFF252, 0x040000); + fmspeedy_set_reg(0xFFF253, 0x00883C); + } else { + fmspeedy_set_reg(0xFFF240, 0x009020); + fmspeedy_set_reg(0xFFF241, 0x004600); + fmspeedy_set_reg(0xFFF242, 0x27365E); + fmspeedy_set_reg(0xFFF243, 0x10BDC8); + fmspeedy_set_reg(0xFFF244, 0x24A0D0); + fmspeedy_set_reg(0xFFF245, 0x018132); + fmspeedy_set_reg(0xFFF246, 0x065A78); + fmspeedy_set_reg(0xFFF247, 0x243100); + fmspeedy_set_reg(0xFFF248, 0x0C0518); + fmspeedy_set_reg(0xFFF249, 0); + fmspeedy_set_reg(0xFFF24B, 0); + fmspeedy_set_reg(0xFFF24C, 0x01F8F4); + fmspeedy_set_reg(0xFFF24D, 0); + fmspeedy_set_reg(0xFFF24E, 0); + fmspeedy_set_reg(0xFFF24F, 0x26081D); + fmspeedy_set_reg(0xFFF250, 0); + fmspeedy_set_reg(0xFFF251, 0x2C0000); + fmspeedy_set_reg(0xFFF252, 0x040000); + fmspeedy_set_reg(0xFFF253, 0x00883C); + } + + API_EXIT(gradio); +} + +void fm_sx_start(void) +{ + API_ENTRY(gradio); + + if (gradio->rfchip_ver == S620_REV_0) { + fmspeedy_set_reg(0xFFF253, 0x0F883C); + udelay(50); + + fmspeedy_set_reg(0xFFF240, 0x3C302A); + udelay(20); + + fmspeedy_set_reg(0xFFF253, 0x0B883C); + fmspeedy_set_reg(0xFFF240, 0x14302A); + } else { + fmspeedy_set_reg(0xFFF253, 0x0F883C); + udelay(50); + + fmspeedy_set_reg(0xFFF240, 0x3C9020); + udelay(20); + + fmspeedy_set_reg(0xFFF253, 0x0B883C); + fmspeedy_set_reg(0xFFF240, 0x149020); + } + + API_ENTRY(gradio); +} + +bool fm_aux_pll_initialize(void) +{ + u32 pll_locked = 0; + u16 i; + + API_ENTRY(gradio); + + if (gradio->rfchip_ver == S620_REV_0) { + fmspeedy_set_reg_field(0xFFF221, 0, 0x0001, 0); /* PLL_CLK_EN, default = 1 */ + udelay(20); + dev_info(gradio->dev, "%s():PLL_CLK_EN[%02X]", + __func__, + fmspeedy_get_reg_field(0xFFF221, 0, 0x0001)); + } + + fmspeedy_set_reg_field(0xFFF255, 10, (0x0001<<10), 1); /* FMCLK_from240M, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 21, (0x0001<<21), 0); /* FMCLK_40_32M, default = 1 */ + fmspeedy_set_reg_field(0xFFF255, 22, (0x0001<<22), 0); /* FMCLK_from260M, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 30, (0x0001<<30), 1); /* PLL_LOCK_EN, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 28, (0x0001<<28), 0); /* PLL_FEED_EN, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 26, (0x0001<<26), 1); /* PLL_EN_CLK_240M, default = 1 */ + fmspeedy_set_reg_field(0xFFF255, 25, (0x0001<<25), 1); /* PLL_EN_CLK_120M, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 27, (0x0001<<27), 1); /* PLL_EN_CLK_80M, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 24, (0x0001<<24), 1); /* PLL_EN, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 23, (0x0001<<23), 1); /* OUT_EN, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 16, (0x0001<<16), 1); /* IREF_EN, default = 1 */ + fmspeedy_set_reg_field(0xFFF255, 9, (0x0001<<9), 1); /* BUFFER_AD_EN, default = 1 */ + fmspeedy_set_reg_field(0xFFF255, 29, (0x0001<<29), 0); /* PLL_FSEL, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 17, (0x000F<<17), 8); /* IREFTRIM, default = 8 */ + fmspeedy_set_reg_field(0xFFF255, 14, (0x0003<<14), 0); /* IO_SPARE, default = 0 */ + fmspeedy_set_reg_field(0xFFF255, 11, (0x0007<<11), 0); /* IO_OUT_SEL, default = 0 */ + +#if 0 + fmspeedy_set_reg_field(0xFFF256, 21, (0x003F<<21), 16); /* PLL1_LPFRBUS, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 19, (0x0003<<19), 3); /* PLL1_LOCK_OUT, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 17, (0x0003<<17), 3); /* PLL1_LOCK_IN, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 15, (0x0003<<15), 3); /* PLL1_LOCK_DLY, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 7, (0x00FF<<7), 10); /* PLL1_FB_DIV, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 4, (0x0007<<4), 7); /* PLL1_CPCBUS, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 3, (0x0001<<3), 0); /* PLL1_BYPASS, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 0, 0x0001, 0); /* PLL_SEL_PLL, default = 0 */ + fmspeedy_set_reg_field(0xFFF256, 1, (0x0003<<1), 0); /* PLL_SPARE, default = 0 */ +#else + fmspeedy_set_reg(0xFFF256, 0x21F8570); +#endif + +#if 0 + fmspeedy_set_reg_field(0xFFF257, 0, 0x003F, 1); /* PLL1_PRE_DIV, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 7, (0x0001<<7), 0); /* PLL1_SEL_CONTROL, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 6, (0x0001<<6), 0); /* PLL1_SEL_BW_TYP, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 9, (0x0003<<9), 1); /* PLL1_VCO_TUNE, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 8, (0x0001<<8), 1); /* PLL1_SEL_HP, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 15, (0x00FF<<15), 12); /* PLL2_FB_DIV, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 27, (0x0003<<27), 3); /* PLL2_LOCK_OUT, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 25, (0x0003<<25), 3); /* _PLL2_LOCK_IN, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 23, (0x0003<<23), 3); /* PLL2_LOCK_DLY, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 12, (0x0007<<12), 4); /* PLL2_CPCBUS, default = 0 */ + fmspeedy_set_reg_field(0xFFF257, 11, (0x0001<<11), 0); /* PLL2_BYPASS, default = 0 */ +#else + fmspeedy_set_reg(0xFFF257, 0x1F864301); +#endif + +#if 0 + fmspeedy_set_reg_field(0xFFF258, 6, (0x003F<<6), 13); /* PLL2_PRE_DIV, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 0, 0x003F, 16); /* PLL2_LPFRBUS, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 15, (0x0003<<15), 1); /* PLL2_VCO_TUNE, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 14, (0x0001<<14), 1); /* PLL2_SEL_HP, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 13, (0x0001<<13), 0); /* PLL2_SEL_CONTROL, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 12, (0x0001<<12), 0); /* PLL2_SEL_BW_TYP, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 19, (0x001F<<19), 0); /* XTAL_AMPLVL, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 27, (0x0001<<27), 1); /* XTAL_EN_FM_BUF, default = 0 */ + fmspeedy_set_reg_field(0xFFF258, 26, (0x0001<<26), 1); /* XTAL_EN_CORE_BUF, default = 1 */ + fmspeedy_set_reg_field(0xFFF258, 24, (0x0001<<24), 1); /* XTAL_EN, default = 1 */ + fmspeedy_set_reg_field(0xFFF258, 17, (0x0001<<17), 1); /* PTAT_EN, default = 1 */ +#else + fmspeedy_set_reg(0xFFF258, 0xD06C350); +#endif + +#if 0 + fmspeedy_set_reg_field(0xFFF259, 0, 0x0001, 1); /* XTAL_IREF_EN, default = 1 */ + fmspeedy_set_reg_field(0xFFF259, 5, (0x0001<<5), 1); /* XTAL_SEL_XTAL, default = 1 */ + fmspeedy_set_reg_field(0xFFF259, 4, (0x0001<<4), 0); /* XTAL_SEL_ULP, default = 0 */ + fmspeedy_set_reg_field(0xFFF259, 3, (0x0001<<3), 0); /* XTAL_SEL_MON, default = 0 */ + fmspeedy_set_reg_field(0xFFF259, 2, (0x0001<<2), 0); /* XTAL_SEL_LP, default = 0 */ + fmspeedy_set_reg_field(0xFFF259, 1, (0x0001<<1), 0); /* XTAL_SEL_ALC, default = 0 */ +#else + fmspeedy_set_reg(0xFFF259, 0x21); +#endif + + for (i = 0; i < 10; i++) { + udelay(300); + pll_locked = fmspeedy_get_reg_field(0xFFF25B, 4, (0x0001 << 4)); + if (pll_locked) { + APIEBUG(gradio, "API> Aux pll lock!!"); + return TRUE; + } + } + dev_err(gradio->dev, "Fail aux pll lock. Check RF power!!"); + + API_EXIT(gradio); + + return FALSE; +} + +void fm_ds_set(u32 data) +{ + fmspeedy_set_reg_field(0xFFF390, 7, (0x0003 << 7), data); + mdelay(10); + dev_info(gradio->v4l2dev.dev, + "%s: DS set: 0x%xh, reg val: 0x%xh\n", __func__, + data, fmspeedy_get_reg(0xFFF390)); +} + +void fm_get_version_number(void) +{ + dev_info(gradio->dev, + ">>> FM version: DEMOD[%04X]ANA[%04X]PMU[%04X]SCG[%04X]SPY[%04X]TOP[%04X]DS[%d]BUILD[%08X]", + fmspeedy_get_reg(0xFFF399), + fmspeedy_get_reg(0xFFF39A), + fmspeedy_get_reg(0xFFF39B), + fmspeedy_get_reg(0xFFF39C), + fmspeedy_get_reg(0xFFF39D), + fmspeedy_get_reg(0xFFF39E), + fmspeedy_get_reg_field(0xFFF390, 7, (0x0003 << 7)), + build_identifier_integer); +} + +void fm_aux_pll_off(void) +{ + fmspeedy_set_reg(0xFFF255, 0x04300308); + fmspeedy_set_reg(0xFFF256, 0); + fmspeedy_set_reg(0xFFF257, 0); + fmspeedy_set_reg(0xFFF258, 0x05040000); + fmspeedy_set_reg(0xFFF259, 0x21); + + if (gradio->rfchip_ver == S620_REV_0) { + fmspeedy_set_reg_field(0xFFF221, 0, 0x0001, 1); /* PLL_CLK_EN, default = 1 */ + dev_info(gradio->dev, "%s():PLL_CLK_EN[%02X]", + __func__, + fmspeedy_get_reg_field(0xFFF221, 0, 0x0001)); + } +} + +/**************************************************************************** + + Functions for tunning + + ****************************************************************************/ + +void fm_set_band(struct s610_radio *radio, u8 index) +{ + u16 num_of_bands = 0; + + num_of_bands = sizeof(radio->low->fm_bands) / sizeof(fm_band_s); + + if (index >= num_of_bands) + index = num_of_bands - 1; + + radio->low->fm_state.band = index; + + radio->low->fm_tuner_state.band_limit_lo = + radio->low->fm_bands[radio->low->fm_state.band].lo; + radio->low->fm_tuner_state.band_limit_hi = + radio->low->fm_bands[radio->low->fm_state.band].hi; + + radio->low->fm_state.freq = radio->low->fm_tuner_state.band_limit_lo; +} + +void fm_set_freq_step(struct s610_radio *radio, u8 index) +{ + radio->low->fm_tuner_state.freq_step = radio->low->fm_freq_steps[index]; +} + +bool fm_band_trim(struct s610_radio *radio, u32 *freq) +{ + bool bl = FALSE; + + if (*freq <= radio->low->fm_tuner_state.band_limit_lo) { + *freq = radio->low->fm_tuner_state.band_limit_lo; + bl = TRUE; + } + if (*freq >= radio->low->fm_tuner_state.band_limit_hi) { + *freq = radio->low->fm_tuner_state.band_limit_hi; + bl = TRUE; + } + + return bl; +} + +static bool fm_tuner_push_freq(struct s610_radio *radio, bool down) +{ + u32 new_freq = radio->low->fm_state.freq; + bool in_bl; + + if (down) + new_freq -= radio->low->fm_tuner_state.freq_step; + else + new_freq += radio->low->fm_tuner_state.freq_step; + + in_bl = fm_band_trim(radio, &new_freq); + radio->low->fm_state.freq = new_freq; + + return in_bl; +} + +static void fm_tuner_enable_rds(struct s610_radio *radio, bool enable) +{ + if (radio->low->fm_state.rds_pwr_on) { + if (enable && !radio->low->fm_state.rds_rx_enabled) + fm_rds_enable(radio); + else if (!enable && radio->low->fm_state.rds_rx_enabled) + fm_rds_disable(radio); + } + radio->low->fm_state.rds_rx_enabled = enable; +} + +void fm_set_rssi_thresh(struct s610_radio *radio, fm_tuner_state state) +{ + switch (state) { + case TUNER_SEARCH: + fmspeedy_set_reg(0xFFF2C4, + radio->low->fm_state.rssi_limit_search); + break; + case TUNER_IDLE: + case TUNER_PRESET: + default: + fmspeedy_set_reg(0xFFF2C4, + radio->low->fm_state.rssi_limit_normal); + break; + } +} + +static void fm_tuner_control_mute(struct s610_radio *radio) +{ + bool mute = radio->low->fm_state.mute_forced + || radio->low->fm_state.mute_audio + || (!radio->low->fm_tuner_state.tune_done); + fm_set_mute(mute); +} + +void fm_tuner_set_mute_audio(struct s610_radio *radio, bool mute) +{ + radio->low->fm_state.mute_audio = mute; + fm_tuner_control_mute(radio); +} + +#ifdef MONO_SWITCH_INTERF +void fm_reset_force_mono_interf(struct s610_radio *radio) +{ + radio->low->fm_state.force_mono_interf = FALSE; + radio->low->fm_state.mono_interf_reset_time = get_time(); + radio->low->fm_state.interf_checked = FALSE; + fm_set_blend_mute(radio); +} + +void fm_check_interferer(struct s610_radio *radio) +{ + s16 rssi; + bool old_state = radio->low->fm_state.force_mono_interf; + TIME passed_time = + get_time() + - radio->low->fm_state.mono_interf_reset_time; + + if (!radio->low->fm_state.interf_checked) { + if (passed_time < (1 * SECOND)) { + return; + } else { + radio->low->fm_state.interf_checked = TRUE; + radio->low->fm_state.mono_interf_reset_time = + get_time(); + radio->low->fm_state.mono_switched_interf = + FALSE; + } + } + + rssi = (radio->low->fm_state.rssi & 0x80) ? + radio->low->fm_state.rssi - 256 : + radio->low->fm_state.rssi; + if ((rssi > radio->low->fm_config.interf_rssi.hi) + && (radio->low->fm_state.snr + < radio->low->fm_config.interf_snr.lo)) { + radio->low->fm_state.force_mono_interf = TRUE; + radio->low->fm_state.mono_switched_interf = TRUE; + } else if ((rssi < radio->low->fm_config.interf_rssi.lo) + || (radio->low->fm_state.snr > + radio->low->fm_config.interf_snr.hi)) { + radio->low->fm_state.force_mono_interf = FALSE; + } + + if (old_state != radio->low->fm_state.force_mono_interf) + fm_set_blend_mute(radio); +} +#endif /* MONO_SWITCH_INTERF */ + +void fm_start_if_counter(void) +{ + fmspeedy_set_reg_field(0xFFF2A9, 5, (0x0001 << 5), 0); + udelay(4); + fmspeedy_set_reg_field(0xFFF2A9, 5, (0x0001 << 5), 1); +} + +static void fm_preset_tuned(struct s610_radio *radio) +{ + u16 count; + int ii; + u16 flag; + + API_ENTRY(radio); + + count = (fmspeedy_get_reg(0xFFF2B2) * 5) / 10; + + fm_tuner_enable_rds(radio, TRUE); + fm_start_if_counter(); + + fmspeedy_set_reg_field(0xFFF302, 0, 1, 1); /* Clear Int. */ + + for (ii = 0; ii < count; ii++) + udelay(1000); /* ms */ + + flag = fm_update_rx_status(radio, fmspeedy_get_reg(0xFFF2CB)); + + fm_update_if_count(radio); + fm_update_sig_info(radio); + + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, TUNER_IDLE); + if (radio->low->fm_tuner_state.hit_band_limit) + flag |= FLAG_BD_LMT; + fm_set_flag_bits(radio, flag | FLAG_TUNED); + /*Set freq completed */ + complete(&radio->flags_set_fr_comp); + APIEBUG(radio, ">>>>> preset tune complete!! 0x%x", + radio->low->fm_state.freq); + + API_EXIT(radio); +} + +static void fm_search_done(struct s610_radio *radio, u16 flags) +{ + API_ENTRY(radio); + fm_tuner_set_mute_audio(radio, FALSE); /* unmute */ + + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, TUNER_IDLE); + APIEBUG(radio, "API 01> Seek done doing complete!! 0x%x, flag 0x%x", + radio->low->fm_state.freq, flags); + + fm_set_flag_bits(radio, flags | FLAG_TUNED); + radio->irq_flag = flags; + + /* Seek_done complete */ + complete(&radio->flags_seek_fr_comp); + + fm_tuner_enable_rds(radio, TRUE); + API_EXIT(radio); +} + +static void fm_search_check_signal2(unsigned long data) +{ +#ifdef USE_NEW_SCAN + static u16 min_weak_ifc_abs; + static u16 min_normal_ifc_abs; +#else + static u16 min_ifc_abs; +#endif + static u16 retry_count; + +#ifdef USE_NEW_SCAN + int rssi; + u16 d_rssi, gain, adjust; +#endif + bool check_ok = TRUE; + bool done = FALSE; + u16 ifc_abs; + struct s610_radio *radio = (void *) data; + + API_ENTRY(radio); + if (radio->sig2_fniarg) { + retry_count = 0; +#ifdef USE_NEW_SCAN + min_weak_ifc_abs = 0xffff; + min_normal_ifc_abs = 0xffff; +#else + min_ifc_abs = 0xffff; +#endif + } + +#ifdef USE_NEW_SCAN + d_rssi = fmspeedy_get_reg(0xFFF2AD); + gain = fmspeedy_get_reg(0xFFF285); + adjust = fmspeedy_get_reg(0xFFF2C2); + + rssi = rssi_device_to_host(d_rssi, gain, adjust); + rssi = (rssi & 0x80) ? rssi - 256 : rssi; + + APIEBUG(radio, "SIG2> rssi %d", rssi); + APIEBUG(radio, "SIG2> weak ifca_m %d", radio->low->fm_config.search_conf.weak_ifca_m); + APIEBUG(radio, "SIG2> normal ifca_m %d", radio->low->fm_config.search_conf.normal_ifca_m); + APIEBUG(radio, "SIG2> RSSI limit %d %d", + aggr_rssi_device_to_host(radio->low->fm_state.rssi_limit_search), + radio->low->fm_state.rssi_limit_search); + APIEBUG(radio, "SIG2> with eLNA %d RSSI ADJUST %x %x", + radio->without_elna, RSSI_ADJUST_WITHOUT_ELNA_VALUE, radio->low->fm_config.rssi_adj_ini); + APIEBUG(radio, "SIG2> seek weak rssi %d", radio->seek_weak_rssi); + APIEBUG(radio, "SIG2> Current Freq %d", radio->low->fm_state.freq); + + if (rssi < radio->seek_weak_rssi) + radio->low->fm_config.search_conf.weak_sig = TRUE; + else + radio->low->fm_config.search_conf.weak_sig = FALSE; + +#endif + +#ifndef USE_NEW_SCAN + if (fmspeedy_get_reg_field(0xFFF2CB, 7, (0x0001 << 7)) != 0) { + done = TRUE; + check_ok = FALSE; + } +#endif + + if (!done) { + ifc_abs = fmspeedy_get_reg(0xFFF2B1); + +#ifdef USE_NEW_SCAN + if (radio->low->fm_config.search_conf.weak_sig) { + if (ifc_abs < radio->low->fm_config.search_conf.weak_ifca_l) { + APIEBUG(radio, "SIG> weak good %d", ifc_abs); + done = TRUE; + check_ok = TRUE; + } + + else if (ifc_abs > radio->low->fm_config.search_conf.weak_ifca_h) { + APIEBUG(radio, "SIG> weak bad %d", ifc_abs); + done = TRUE; + check_ok = FALSE; + } + + else if (ifc_abs < min_weak_ifc_abs) { + APIEBUG(radio, "SIG> weak mid %d %d", ifc_abs, min_weak_ifc_abs); + min_weak_ifc_abs = ifc_abs; + } + } else { + if (ifc_abs < radio->low->fm_config.search_conf.normal_ifca_l) { + APIEBUG(radio, "SIG> normal good %d", ifc_abs); + done = TRUE; + check_ok = TRUE; + } + + else if (ifc_abs > radio->low->fm_config.search_conf.normal_ifca_h) { + APIEBUG(radio, "SIG> normal bad %d", ifc_abs); + done = TRUE; + check_ok = FALSE; + } + + else if (ifc_abs < min_normal_ifc_abs) { + APIEBUG(radio, "SIG> normal mid %d %d", ifc_abs, min_weak_ifc_abs); + min_normal_ifc_abs = ifc_abs; + } + } +#else + if (ifc_abs < radio->low->fm_config.search_conf.ifca_l) { + done = TRUE; + check_ok = TRUE; + } else if (ifc_abs > radio->low->fm_config.search_conf.ifca_h) { + done = TRUE; + check_ok = FALSE; + } else if (ifc_abs < min_ifc_abs) { + min_ifc_abs = ifc_abs; + } +#endif + } + +#ifdef USE_NEW_SCAN + if ((!done) && (++retry_count >= 5)) { + done = TRUE; + + if (((min_weak_ifc_abs < 0xffff) + && (min_weak_ifc_abs > radio->low->fm_config.search_conf.weak_ifca_m)) + || ((min_normal_ifc_abs < 0xffff) + && (min_normal_ifc_abs > radio->low->fm_config.search_conf.normal_ifca_m))) { + check_ok = FALSE; + APIEBUG(radio, "SIG> check mid fail"); + } + } +#else + if ((!done) && (++retry_count >= 10)) { + + done = TRUE; + if (min_ifc_abs > + radio->low->fm_config.search_conf.ifca_m) + check_ok = FALSE; + } +#endif + + if (done) { + if (check_ok) { + u16 flag = + fm_update_rx_status(radio, + fmspeedy_get_reg(0xFFF2CB)); + + fm_update_if_count(radio); + fm_update_sig_info(radio); + fm_search_done(radio, flag); + } else { + fm_search_check_signal1(radio, TRUE); + } + + } else { + radio->sig2_fniarg = FALSE; + radio->dwork_sig2_counter++; + schedule_delayed_work(&radio->dwork_sig2, + msecs_to_jiffies(SEARCH_DELAY_MS)); + } + + API_EXIT(radio); +} + +static void fm_search_check_signal1(struct s610_radio *radio, bool rssi_oor) +{ + u16 flag; + u16 d_status; + + /* API_ENTRY(radio);*/ +#ifndef RDS_POLLING_ENABLE + fm_set_handler_if_count(NULL); +#endif /* RDS_POLLING_ENABLE */ + + d_status = fmspeedy_get_reg(0xFFF2CB); + + if (rssi_oor || !!(d_status & (0x0001 << 7))) { + if (radio->low->fm_tuner_state.hit_band_limit) { + APIEBUG(radio, "SIG1> IF_OOR 0x%x,0x%x,0x%x", + radio->wrap_around, radio->seek_freq, + radio->low->fm_state.freq); + if (radio->wrap_around) { + if (radio->seek_freq == radio->low->fm_state.freq) { + flag = fm_update_rx_status(radio, d_status); + fm_update_if_count(radio); + fm_update_sig_info(radio); + + fm_search_done(radio, flag); + } else { + radio->low->fm_tuner_state.hit_band_limit = + fm_tuner_push_freq( + radio, + radio->low->fm_tuner_state.curr_search_down); + /* disable audio out */ + fm_audio_control(radio, 0, 1, 0x100, 0x1A0); + + fm_set_freq(radio, radio->low->fm_state.freq, 1); + + /* enable audio out */ + fm_audio_control(radio, 1, 1, 0x100, 0x1A0); + + radio->tune_fniarg = 0; + radio->dwork_tune_counter++; + schedule_delayed_work(&radio->dwork_tune, + msecs_to_jiffies(TUNE_TIME_FAST_MS)); + } + } else { + flag = fm_update_rx_status(radio, d_status); + fm_update_if_count(radio); + fm_update_sig_info(radio); + + if (radio->seek_freq == radio->low->fm_state.freq) + fm_search_done(radio, flag); + else + fm_search_done(radio, flag | FLAG_BD_LMT); + + APIEBUG(radio, "SIG1> IF_Not OOR 0x%x,0x%x,0x%x,0x%x", + radio->wrap_around, radio->seek_freq, + radio->low->fm_state.freq, flag); + } + } else { + if (radio->wrap_around && + (radio->seek_freq == radio->low->fm_state.freq)) { + flag = fm_update_rx_status(radio, d_status); + fm_update_if_count(radio); + fm_update_sig_info(radio); + fm_search_done(radio, flag); + } else { + radio->low->fm_tuner_state.hit_band_limit = + fm_tuner_push_freq( + radio, + radio->low->fm_tuner_state.curr_search_down); + /* disable audio out */ + fm_audio_control(radio, 0, 1, 0x100, 0x1A0); + + fm_set_freq(radio, radio->low->fm_state.freq, 1); + + /* enable audio out */ + fm_audio_control(radio, 1, 1, 0x100, 0x1A0); + radio->tune_fniarg = 0; + radio->dwork_tune_counter++; + schedule_delayed_work(&radio->dwork_tune, + msecs_to_jiffies(TUNE_TIME_FAST_MS)); + } + } + } else { + radio->sig2_fniarg = 1; + radio->dwork_sig2_counter++; + schedule_delayed_work(&radio->dwork_sig2, + msecs_to_jiffies(SEARCH_DELAY_MS)); + } + /* API_EXIT(radio);*/ +} + +static void fm_search_tuned(unsigned long data) +{ + u16 count, ii; + struct s610_radio *radio = (void *) data; + + API_ENTRY(radio); + + count = (fmspeedy_get_reg(0xFFF2B2) * 5) / 10; + + if (fm_check_rssi_level(radio->low->fm_state.rssi_limit_search)) { + fm_search_check_signal1(radio, TRUE); + } else { + fm_start_if_counter(); + + fmspeedy_set_reg_field(0xFFF302, 0, 1, 1); /* Clear Int. */ + + for (ii = 0; ii < count; ii++) + udelay(1000); /* ms */ + + fm_search_check_signal1(radio, FALSE); + } + + API_EXIT(radio); +} + +#ifdef USE_FILTER_SELECT_BY_FREQ +static const u32 filter_freq_very[MAX_FILTER_FREQ_NUM] = { + 87900, 88100, 95900, 96100, 103900, 104100 +}; + +static bool is_freq_in_array(int freq) { + int i; + + for (i = 0; i < MAX_FILTER_FREQ_NUM; i++) { + if (filter_freq_very[i] == freq) + return TRUE; + } + return FALSE; +} +#endif /* USE_FILTER_SELECT_BY_FREQ */ + +#ifdef RDS_POLLING_ENABLE +void fm_rds_periodic_cancel(unsigned long data) +{ + struct s610_radio *radio = (struct s610_radio *) data; + + cancel_delayed_work(&radio->dwork_rds_poll); +} + +void fm_rds_periodic_update(unsigned long data) +{ + struct s610_radio *radio = (struct s610_radio *) data; + + radio->dwork_rds_counter++; + schedule_delayed_work(&radio->dwork_rds_poll, + msecs_to_jiffies(RDS_POLL_DELAY_MS)); +} +#endif /*RDS_POLLING_ENABLE*/ + +#ifdef IDLE_POLLING_ENABLE +void fm_idle_periodic_cancel(unsigned long data) +{ + struct s610_radio *radio = (struct s610_radio *) data; + + cancel_delayed_work(&radio->dwork_idle_poll); +} + +void fm_idle_periodic_update(unsigned long data) +{ + struct s610_radio *radio = (struct s610_radio *) data; + + radio->dwork_idle_counter++; + schedule_delayed_work(&radio->dwork_idle_poll, + msecs_to_jiffies(IDLE_TIME_MS)); +} +#endif /*IDLE_POLLING_ENABLE*/ + +static void fm_start_tune(struct s610_radio *radio, fm_tuner_state new_state) +{ + bool next = !!(radio->low->fm_state.tuner_mode & TUNER_MODE_MASK_NEXT); + + API_ENTRY(radio); + + switch (new_state) { + case TUNER_NOTTUNED: + break; + case TUNER_IDLE: + radio->low->fm_tuner_state.tune_done = TRUE; + +#ifdef USE_IQ_IMBAL_SMOOTH + hal_set_fm_image_trim_smooth_config_lock(1); +#endif /*USE_IQ_IMBAL_SMOOTH*/ + +#ifdef USE_FILTER_SELECT_BY_FREQ + if (is_freq_in_array(radio->low->fm_state.freq)) + /* Set the filter to use very narrow band */ + fmspeedy_set_reg(0xFFF2AE, 0x0DB6); + else + /* Set the filter to use narrow band */ + fmspeedy_set_reg(0xFFF2AE, 0x0B6D); +#else + fmspeedy_set_reg(0xFFF2AE, 0x0B6D); +#endif /* USE_FILTER_SELECT_BY_FREQ */ + + if (!radio->low->fm_state.tuner_mode) + radio->low->fm_state.mute_audio = 0; + fm_tuner_control_mute(radio); + +#ifdef MONO_SWITCH_INTERF + fm_reset_force_mono_interf(radio); +#endif + break; + case TUNER_PRESET: + fm_tuner_enable_rds(radio, FALSE); + radio->low->fm_tuner_state.hit_band_limit = FALSE; + if (next) + radio->low->fm_tuner_state.hit_band_limit = + fm_tuner_push_freq( + radio, + radio->low->fm_tuner_state.curr_search_down); + + /* disable audio out */ + fm_audio_control(radio, 0, 1, 0x100, 0x1A0); + + fm_set_freq(radio, radio->low->fm_state.freq, 1); + + /* enable audio out */ + fm_audio_control(radio, 1, 1, 0x100, 0x1A0); + + mdelay(TUNE_TIME_SLOW_MS); + fm_preset_tuned(radio); + + break; + case TUNER_SEARCH: + fm_tuner_enable_rds(radio, FALSE); + fm_tuner_set_mute_audio(radio, TRUE); + fm_set_rssi_thresh(radio, new_state); + radio->low->fm_tuner_state.hit_band_limit = FALSE; + + if (next) { + radio->low->fm_tuner_state.hit_band_limit = + fm_tuner_push_freq( + radio, + radio->low->fm_tuner_state.curr_search_down); + } + + /* disable audio out */ + fm_audio_control(radio, 0, 1, 0x100, 0x1A0); + + fm_set_freq(radio, radio->low->fm_state.freq, 1); + + /* enable audio out */ + fm_audio_control(radio, 1, 1, 0x100, 0x1A0); + + radio->tune_fniarg = 0; + radio->dwork_tune_counter++; + schedule_delayed_work(&radio->dwork_tune, + msecs_to_jiffies(TUNE_TIME_FAST_MS)); + break; + default: + break; + } + + API_EXIT(radio); + +} + +static void fm_tuner_change_state(struct s610_radio *radio, + fm_tuner_state new_state) +{ + radio->low->fm_tuner_state.tuner_state = new_state; + fm_update_tuner_mode(radio); + + switch (new_state) { + case TUNER_OFF: + break; + case TUNER_NOTTUNED: + radio->low->fm_tuner_state.tune_done = FALSE; + fm_tuner_enable_rds(radio, FALSE); + break; + case TUNER_IDLE: + case TUNER_PRESET: + case TUNER_SEARCH: + fm_start_tune(radio, new_state); + break; + } +} + +static void fm_cancel_delayed_work(struct s610_radio *radio) +{ + cancel_delayed_work(&radio->dwork_sig2); + cancel_delayed_work(&radio->dwork_tune); +} + +static void fm_tuner_exit_state(struct s610_radio *radio) +{ + fm_cancel_delayed_work(radio); +#ifndef RDS_POLLING_ENABLE + fm_set_handler_if_count(NULL); + fm_set_handler_audio_pause(NULL); +#endif /*RDS_POLLING_ENABLE*/ + fm_set_rssi_thresh(radio, TUNER_IDLE); +} + +void fm_set_tuner_mode(struct s610_radio *radio) +{ + u8 tm; + fm_tuner_state new_state; + + API_ENTRY(radio); + + if (!radio->low->fm_state.fm_pwr_on) { + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, TUNER_OFF); + } else { + tm = radio->low->fm_state.tuner_mode & TUNER_MODE_MASK_TUN_MOD; + new_state = + radio->low->fm_tuner_state.tune_done ? + TUNER_IDLE : TUNER_NOTTUNED; + + switch (tm) { + case TUNER_MODE_PRESET: + new_state = TUNER_PRESET; + break; + case TUNER_MODE_SEARCH: + new_state = TUNER_SEARCH; + break; + case TUNER_MODE_NONE: + default: + break; + } + + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, new_state); + } + + API_EXIT(radio); +} + +static bool fm_tuner_on(struct s610_radio *radio) +{ + API_ENTRY(radio); + + if (!fm_radio_on(radio)) { + radio->low->fm_state.fm_pwr_on = + radio->low->fm_state.rds_pwr_on = + FALSE; + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, TUNER_OFF); + + return FALSE; + } + + API_EXIT(radio); + + return TRUE; +} + +static void fm_tuner_off(struct s610_radio *radio) +{ + fm_radio_off(radio); +} + +void fm_tuner_rds_on(struct s610_radio *radio) +{ + fm_rds_on(radio); + + if (radio->low->fm_state.rds_rx_enabled) + fm_rds_enable(radio); +} + +void fm_tuner_rds_off(struct s610_radio *radio) +{ + if (radio->low->fm_state.rds_rx_enabled) + fm_rds_disable(radio); + + fm_rds_off(radio); +} + +bool fm_tuner_set_power_state(struct s610_radio *radio, bool fm_on, bool rds_on) +{ + + API_ENTRY(radio); + + if (fm_on && !radio->low->fm_state.fm_pwr_on) { + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, TUNER_NOTTUNED); + fm_tuner_control_mute(radio); + } else if (!fm_on && radio->low->fm_state.fm_pwr_on) { + fm_tuner_exit_state(radio); + fm_tuner_change_state(radio, TUNER_OFF); + } + + if (fm_on && !radio->low->fm_state.fm_pwr_on) { + if (!fm_tuner_on(radio)) + return FALSE; + + radio->low->fm_state.fm_pwr_on = TRUE; + } + + if (rds_on && !radio->low->fm_state.rds_pwr_on) { + if (radio->low->fm_state.fm_pwr_on) { + fm_tuner_rds_on(radio); + radio->low->fm_state.rds_pwr_on = TRUE; + } + } else if ((!rds_on || !fm_on) && radio->low->fm_state.rds_pwr_on) { + fm_tuner_rds_off(radio); + radio->low->fm_state.rds_pwr_on = FALSE; + } + + if (!fm_on && radio->low->fm_state.fm_pwr_on) { + fm_tuner_off(radio); + radio->low->fm_state.fm_pwr_on = FALSE; + fm_tuner_enable_rds(radio, FALSE); + } + + API_EXIT(radio); + + return TRUE; +} + + +fm_conf_ini_values low_fm_conf_init = { + .demod_conf_ini = 0x228C, + .rssi_adj_ini = 0x006E, + .soft_muffle_conf_ini = { 0x2516, 1, 1, 7 }, + .soft_mute_atten_max_ini = 0x0007, + .stereo_thres_ini = 0x00C8, + .narrow_thres_ini = 0x0074, + .snr_adj_ini = 0x001C, + .snr_smooth_conf_ini = 0x082F, + .mute_coeffs_soft = 0x2516, + .mute_coeffs_dis = 0x0000, + .blend_coeffs_soft = 0x095A, + .blend_coeffs_switch = 0x7D8C, + .blend_coeffs_dis = 0x00FF, + .rds_int_byte_count = RDS_MEM_MAX_THRESH, +#ifdef USE_NEW_SCAN + /*.search_conf = { 4000, 5000, 7000, 3000, 3800, 5600, FALSE},*/ + .search_conf = { 4500, 6000, 7000, 4000, 4800, 5600, FALSE}, +#else + .search_conf = { 4100, 4700, 5500 }, +#endif +#ifdef MONO_SWITCH_INTERF + .interf_rssi = { -85, -75 }, + .interf_snr = { 20, 43 }, +#endif + .rds_error_limit = 3 +}; + +fm_state_s low_fm_state_init = { + .rds_rx_enabled = FALSE, + .fm_pwr_on = FALSE, + .rds_pwr_on = FALSE, + .force_mono = FALSE, + .use_switched_blend = FALSE, + .use_soft_mute = TRUE, + .mute_forced = FALSE, + .mute_audio = FALSE, + .search_down = FALSE, + .use_rbds = FALSE, + .save_eblks = FALSE, + .last_status_blend_stereo = FALSE, + .last_status_rds_sync = FALSE, +#ifdef MONO_SWITCH_INTERF + .force_mono_interf = FALSE, + .interf_checked = FALSE, + .mono_switched_interf = FALSE, + .mono_interf_reset_time = 0, +#endif + .tuner_mode = 0, + .status = 0, + .rds_mem_thresh = 0, + .rssi = 0, + .band = 0, + .last_ifc = 0, + .snr = 0, + .rssi_limit_normal = 0, + .rssi_limit_search = 0, + .freq = 0, + .flags = 0, + .rds_unsync_uncorr_weight = 10, + .rds_unsync_blk_cnt = 20, + .rds_unsync_bit_cnt = 48 +}; + +fm_tuner_state_s low_fm_tuner_state_init = { + .tuner_state = TUNER_OFF, + .curr_search_down = FALSE, + .hit_band_limit = FALSE, + .tune_done = FALSE, + .freq_step = 100, + .band_limit_lo = 87500, + .band_limit_hi = 108000 +}; + +fm_band_s fm_bands_init[] = { { 87500, 108000 }, { 76000, 90000 } }; +u16 fm_freq_steps_init[] = { 50, 100, 200 }; +#ifdef USE_SPUR_CANCEL +extern u32 *fm_spur_init; +#endif +#ifdef USE_S612_DUAL_CLOCKING +u32 filter_freq_spur_case_1[] = { + 87900, 88000, 88100, 95900, 96000, 96100, 99900, 100100 }; +#endif /* USE_S612_DUAL_CLOCKING */ + +#ifdef USE_SPUR_CANCEL_TRF +u32 filter_freq_spur_select[] = { + 98300, 100000, 104000 }; +#endif /* USE_SPUR_CANCEL_TRF */ + +int init_low_struc(struct s610_radio *radio) +{ + memcpy(&radio->low->fm_config, &low_fm_conf_init, + sizeof(fm_conf_ini_values)); + memcpy(&radio->low->fm_state, &low_fm_state_init, sizeof(fm_state_s)); + memcpy(&radio->low->fm_tuner_state, &low_fm_tuner_state_init, + sizeof(fm_tuner_state_s)); + memcpy(&radio->low->fm_bands, &fm_bands_init, sizeof(fm_band_s) * 2); + memcpy(&radio->low->fm_freq_steps, + &fm_freq_steps_init, sizeof(u16) * 3); + if (radio->sw_mute_weak) { + radio->low->fm_config.soft_muffle_conf_ini.muffle_coeffs = 0x1B16; + radio->low->fm_config.mute_coeffs_soft = 0x1B16; + } +#ifdef USE_SPUR_CANCEL + if (radio->tc_on) + memcpy(radio->low->fm_spur, fm_spur_init, + sizeof(u32) * radio->tc_on); +#endif +#ifdef USE_S612_DUAL_CLOCKING + if ((!radio->dual_clk_on) && (radio->rfchip_ver == S620_REV_0)) { + memcpy(radio->low->fm_dual_clk, filter_freq_spur_case_1, + sizeof(filter_freq_spur_case_1)); + fm_dual_clk_init = radio->low->fm_dual_clk; + radio->dual_clk_on = sizeof(filter_freq_spur_case_1)/sizeof(u32); + } +#endif /* USE_S612_DUAL_CLOCKING */ +#ifdef USE_SPUR_CANCEL_TRF + if ((!radio->trf_on) && (radio->rfchip_ver == S620_REV_0)) { + memcpy(radio->low->fm_spur_trf, filter_freq_spur_select, + sizeof(filter_freq_spur_select)); + fm_spur_trf_init = radio->low->fm_spur_trf; + radio->trf_on = sizeof(filter_freq_spur_select)/sizeof(u32); + } +#endif /* USE_SPUR_CANCEL_TRF */ + + return 0; +} diff --git a/drivers/media/radio/s610/fm_low_ref.h b/drivers/media/radio/s610/fm_low_ref.h new file mode 100644 index 000000000000..c1238244568b --- /dev/null +++ b/drivers/media/radio/s610/fm_low_ref.h @@ -0,0 +1,163 @@ +#ifndef FM_LOW_REF_H +#define FM_LOW_REF_H + +#include +#include +#include +#include + +/****************************************************************************** + * definition + ******************************************************************************/ +#define ABS(a) (((a) < 0) ? -(a) : (a)) + +int fm_boot(struct s610_radio *radio); +void fm_power_off(void); +u16 if_count_device_to_host(struct s610_radio *radio, u16 val); +static u16 aggr_rssi_host_to_device(u8 val); +u8 aggr_rssi_device_to_host(u16 val); +u16 rssi_device_to_host(u16 digi_rssi, u16 agc_gain, u16 rssi_adj); +#ifdef USE_SPUR_CANCEL +void fm_rx_en_spur_removal(struct s610_radio *radio); +void fm_rx_dis_spur_removal(void); +void fm_rx_check_spur(struct s610_radio *radio); +void fm_rx_check_spur_mono(struct s610_radio *radio); +#endif +void fm_set_freq(struct s610_radio *radio, u32 freq, bool mix_hi); +void fm_set_mute(bool mute); +void fm_set_blend_mute(struct s610_radio *radio); +static void fm_rds_flush_buffers(struct s610_radio *radio, + bool clear_buffer); +void fm_rds_enable(struct s610_radio *radio); +void fm_rds_disable(struct s610_radio *radio); +bool fm_radio_on(struct s610_radio *radio); +void fm_radio_off(struct s610_radio *radio); +void fm_rds_on(struct s610_radio *radio); +void fm_rds_off(struct s610_radio *radio); +void fm_initialize(struct s610_radio *radio); +u16 fm_get_flags(struct s610_radio *radio); +void fm_set_flags(struct s610_radio *radio, u16 flags); +void fm_set_handler_if_count(void (*fn)(struct s610_radio *radio)); +void fm_set_handler_audio_pause(void (*fn)(struct s610_radio *radio)); +void fm_update_if_count(struct s610_radio *radio); +void fm_update_if_count_int(struct s610_radio *radio); +void fm_update_rssi(struct s610_radio *radio); +void fm_update_rssi_work(struct s610_radio *radio); +void fm_update_snr(struct s610_radio *radio); +void fm_update_sig_info(struct s610_radio *radio); +void fm_update_rds_sync_status(struct s610_radio *radio, + bool synced); +u16 fm_update_rx_status(struct s610_radio *radio, u16 d_status); +void fm_update_tuner_mode(struct s610_radio *radio); +bool fm_check_rssi_level(u16 limit); +int low_get_search_lvl(struct s610_radio *radio, u16 *value); +int low_set_if_limit(struct s610_radio *radio, u16 value); +int low_set_search_lvl(struct s610_radio *radio, u16 value); +int low_set_freq(struct s610_radio *radio, u32 value); +int low_set_tuner_mode(struct s610_radio *radio, u16 value); +int low_set_mute_state(struct s610_radio *radio, u16 value); +int low_set_most_mode(struct s610_radio *radio, u16 value); +int low_set_most_blend(struct s610_radio *radio, u16 value); +int low_set_pause_lvl(struct s610_radio *radio, u16 value); +int low_set_pause_dur(struct s610_radio *radio, u16 value); +int low_set_demph_mode(struct s610_radio *radio, u16 value); +int low_set_rds_cntr(struct s610_radio *radio, u16 value); +int low_set_power(struct s610_radio *radio, u16 value); + +void fm_set_interrupt_source(u16 sources, bool enable); +void fm_isr(struct s610_radio *radio); +void fm_rx_ana_start(void); +void fm_rx_ana_stop(void); +void fm_setup_iq_imbalance(void); +void fm_rx_init(void); + +void fm_lo_off(void); +void fm_lo_prepare_setup(struct s610_radio *radio); +void fm_lo_set(const struct_fm_lo_setup lo_set); +void fm_lo_initialize(struct s610_radio *radio); +void fm_sx_reset(void); +void fm_sx_start(void); +bool fm_aux_pll_initialize(void); +void fm_aux_pll_off(void); +void fm_set_band(struct s610_radio *radio, u8 index); +void fm_set_freq_step(struct s610_radio *radio, u8 index); +bool fm_band_trim(struct s610_radio *radio, u32 *freq); +void fm_get_if_filter_config(struct s610_radio *radio); +static bool fm_tuner_push_freq(struct s610_radio *radio, + bool down); +static void fm_tuner_enable_rds(struct s610_radio *radio, + bool enable); +void fm_set_rssi_thresh(struct s610_radio *radio, + fm_tuner_state state); +static void fm_tuner_control_mute(struct s610_radio *radio); +void fm_tuner_set_force_mute(struct s610_radio *radio, bool mute); +void fm_tuner_set_mute_audio(struct s610_radio *radio, bool mute); +#ifdef MONO_SWITCH_INTERF +void fm_check_interferer(struct s610_radio *radio); +void fm_reset_force_mono_interf(struct s610_radio *radio); +#endif +void fm_timer_reset(fm_timer_t *timer, int usec, + fm_callback_t *func, void *arg); +void fm_start_if_counter(void); +static void fm_preset_tuned(struct s610_radio *radio); +static void fm_search_done(struct s610_radio *radio, u16 flags); +static void fm_search_check_signal2(unsigned long data); +static void fm_search_check_signal1(struct s610_radio *radio, bool rssi_oor); +static void fm_search_tuned(unsigned long data); +static void fm_start_tune(struct s610_radio *radio, + fm_tuner_state new_state); +static void fm_tuner_change_state(struct s610_radio *radio, + fm_tuner_state new_state); +void cancel_tuner_timer(struct s610_radio *radio); +static void fm_tuner_exit_state(struct s610_radio *radio); +void fm_set_tuner_mode(struct s610_radio *radio); +static bool fm_tuner_on(struct s610_radio *radio); +static void fm_tuner_off(struct s610_radio *radio); +void fm_tuner_rds_on(struct s610_radio *radio); +void fm_tuner_rds_off(struct s610_radio *radio); +bool fm_tuner_set_power_state(struct s610_radio *radio, + bool fm_on, bool rds_on); +int init_low_struc(struct s610_radio *radio); +void fm_iclkaux_set(u32 data); + +#ifdef ENABLE_RDS_WORK_QUEUE +void s610_rds_work(struct work_struct *work); +#endif /*ENABLE_RDS_WORK_QUEUE*/ +#ifdef ENABLE_IF_WORK_QUEUE +void s610_if_work(struct work_struct *work); +#endif /*ENABLE_RDS_WORK_QUEUE*/ + +void s610_sig2_work(struct work_struct *work); +void s610_tune_work(struct work_struct *work); + +#ifdef RDS_POLLING_ENABLE +void fm_rds_periodic_update(unsigned long data); +void fm_rds_periodic_cancel(unsigned long data); +#endif /*RDS_POLLING_ENABLE*/ +#ifdef IDLE_POLLING_ENABLE +void fm_idle_periodic_update(unsigned long data); +void fm_idle_periodic_cancel(unsigned long data); +#endif /*IDLE_POLLING_ENABLE*/ +void fm_set_audio_gain(struct s610_radio *radio, u16 gain); +void fm_ds_set(u32 data); +void fm_get_version_number(void); +extern void fmspeedy_wakeup(void); +extern void fm_pwron(void); +extern void fm_pwroff(void); +extern int fmspeedy_set_reg_field( + u32 addr, u32 shift, u32 mask, u32 data); +extern int fmspeedy_set_reg_field_work( + u32 addr, u32 shift, u32 mask, u32 data); +extern int fmspeedy_set_reg(u32 addr, u32 data); +extern int fmspeedy_set_reg_work(u32 addr, u32 data); +extern u32 fmspeedy_get_reg(u32 addr); +extern u32 fmspeedy_get_reg_work(u32 addr); +extern u32 fmspeedy_get_reg_field(u32 addr, u32 shift, u32 mask); +extern u32 fmspeedy_get_reg_field_work(u32 addr, u32 shift, u32 mask); +extern void fm_audio_control(struct s610_radio *radio, + bool audio_out, bool lr_switch, + u32 req_time, u32 audio_addr); +extern int fm_read_rds_data(struct s610_radio *radio, u8 *buffer, int size, + u16 *blocks); +extern void fm_process_rds_data(struct s610_radio *radio); +#endif /*FM_LOW_REF_H*/ diff --git a/drivers/media/radio/s610/fm_low_struc.h b/drivers/media/radio/s610/fm_low_struc.h new file mode 100644 index 000000000000..5106af56aa73 --- /dev/null +++ b/drivers/media/radio/s610/fm_low_struc.h @@ -0,0 +1,487 @@ +#ifndef FM_LOW_STRUC_H +#define FM_LOW_STRUC_H + +#include +#include +#include +#include +#include + +#define S620_REV_0 (0x100000) + +#define USE_SPUR_CANCEL +#undef USE_SPUR_CANCEL + +/* S612 Rev1 SPUR TRF Default define */ +#define USE_SPUR_CANCEL_TRF +/*#undef USE_SPUR_CANCEL_TRF*/ + +/* S612 Rev1 Dual clocking Default define */ +#define USE_S612_DUAL_CLOCKING +/*#undef USE_S612_DUAL_CLOCKING*/ + +#define USE_FILTER_SELECT_BY_FREQ +#define MAX_FILTER_FREQ_NUM 6 + +#define USE_NEW_SCAN +#define USE_RDS_HW_DECODER +#undef USE_RDS_HW_DECODER + +#define USE_RINGBUFF_API +/*#undef USE_RINGBUFF_API*/ + +#define FM_LOW_DRV_DELAY_MS 1 +#define AGGR_RSSI_OFFSET (-114) +#define RDS_VALID_THRESHOLD (140) /* -104dB */ + +#undef TRUE +#define TRUE (1) + +#undef FALSE +#define FALSE (0) + +typedef u32 TIME; + +/* TIME constants. */ + +#define SECOND HZ +#define IDLE_TIME_MS (300) +#define RDS_POLL_DELAY_MS (150) +#define TUNE_TIME_FAST_MS (30) +#define TUNE_TIME_SLOW_MS (60) +#ifdef USE_NEW_SCAN +#define SEARCH_DELAY_MS (20) +#else +#define SEARCH_DELAY_MS (15) +#endif + +#define RSSI_REF_ENABLE 0x01 +#define FM_RDS_MEM_SIZE_PARSER 1000 +#define FM_RDS_MEM_SIZE 480 +#define RDS_PARSER_ENABLE 0x04 +#define FM_RADIO_RDS_PARSER_VER_CHECK 0x400 + +#ifdef USE_SPUR_CANCEL +#define EN_SPUR_REMOVAL (0x0001) +#define DIS_SPUR_REMOVAL_MONO (0x0002) +#endif + +#define RSSI_ADJUST_WITHOUT_ELNA_VALUE (68) +#define SNR_OFF_RSSI_VALUE (174) +#define SNR_ON_RSSI_VALUE (171) +#define TRF_OFF_RSSI_VALUE (202) +#define TRF_ON_RSSI_VALUE (199) +#define SCAN_WEAK_SIG_THRESHOLD (-103) + +/****************************************************************************** + * DEFINITIONS AND MACROS + ******************************************************************************/ +/* == FM SPEEDY registers == */ +#define FM_SPEEDY_MA_BASE (0x14840000) +#define FMSPDY_CTL (0x00000000) +#define FMSPDY_STAT (0x00000004) +#define FMSPDY_DISR (0x00000008) +#define FMSPDY_INTR_MASK (0x0000000C) +#define FMSPDY_DATA (0x00000010) +#define FMSPDY_CMD (0x00000014) +#define FMSPDY_ERR_CNT (0x00000018) +#define FMSPDY_MISC_STAT (0x0000001C) +#define FMSPDY_PRAMS (0x00000020) +#define FM_SLV_INT (0x00000040) +#define AUDIO_CTRL (0x00000024) +#define AUDIO_FIFO (0x00000028) +#define FM_SPEEDY_MA_SIZE 1024 + +/* FMSPDY INT Mask bits */ +#define FM_SLV_INT_MASK_BIT (5) + +/* FMSPDY Control Register Flags */ +#define SPDY_WAKEUP (1<<23) + +/* FMSPDY Status Register Flags*/ +#define RX_FMT_ERR (1<<4) +#define RX_NO_STOP (1<<3) +#define RX_GLITCHED (1<<2) +#define RX_TIMEOUT (1<<1) +#define RX_ALL_ERR (RX_FMT_ERR|RX_NO_STOP|RX_GLITCHED|RX_TIMEOUT) +#define STAT_DONE (1<<0) + +/* FMSPDY Command Register Flags*/ +#define FMSPDY_READ (0<<17) +#define FMSPDY_WRITE (1<<17) +#define FMSPDY_RANDOM (1<<16) + +#define write32(addr, data) __raw_writel(data, addr) +#define read32(addr) __raw_readl((volatile void __iomem *)addr) +#define SetBits(uAddr, uBaseBit, uMaskValue, uSetValue) \ + write32(uAddr, (read32(uAddr) & ~((uMaskValue)<<(uBaseBit))) | \ + (((uMaskValue)&(uSetValue))<<(uBaseBit))) +#define GetBits(uAddr, uBaseBit, uMaskValue) \ + ((read32(uAddr)>>(uBaseBit))&(uMaskValue)) + + +enum flags_enum { + FLAG_TUNED = (1 << 0), + FLAG_BD_LMT = (1 << 1), + FLAG_SYN_LOS = (1 << 2), + FLAG_BUF_FUL = (1 << 3), + FLAG_AUD_PAU = (1 << 4), + FLAG_CH_STAT = (1 << 5) +}; + +enum fm_status_mask_enum { + STATUS_MASK_RDS_AVA = (1 << 0), + STATUS_MASK_STEREO = (1 << 1) +}; + +enum fm_search_dir_mask_enum { + SEARCH_DIR_MASK = (1 << 0) +}; + +enum fm_tuner_mode_mask_enum { + TUNER_MODE_MASK_TUN_MOD = (7 << 0), + TUNER_MODE_MASK_NEXT = (1 << 3) +}; + +enum fm_tuner_mode_enum { + TUNER_MODE_NONE = 0, + TUNER_MODE_PRESET = 1, + TUNER_MODE_SEARCH = 2 +}; + +enum fm_mute_state_mask_enum { + MUTE_STATE_MASK_SOFT = (1 << 0), + MUTE_STATE_MASK_HARD = (1 << 1) +}; + +enum fm_output_mode_mask_enum { + MODE_MASK_MONO_STEREO = (1 << 0) +}; + +enum fm_blend_mode_mask_enum { + MODE_MASK_BLEND = (1 << 0) +}; + +enum fm_rds_ctrl_mask_enum { + RDS_CTRL_MASK_FLUSH = (1 << 0), + RDS_CTRL_MASK_RESYNC = (1 << 1) +}; + +enum fm_rds_system_mask_enum { + RDS_SYSTEM_MASK_RDS = (1 << 0), + RDS_SYSTEM_MASK_EBLK = (1 << 1) +}; + +enum fm_power_mask_enum { + PWR_MASK_FM = (1 << 0), + PWR_MASK_RDS = (1 << 1) +}; + +enum fm_deemph_mode_mask_enum { + MODE_MASK_DEEMPH = (1 << 0) +}; + +typedef enum { + HOST_RDS_ERRS_NONE = 0, + HOST_RDS_ERRS_2CORR = 1, + HOST_RDS_ERRS_5CORR = 2, + HOST_RDS_ERRS_UNCORR = 3 +} fm_host_rds_errors_enum; + +#define RDS_MEM_MAX_THRESH (48) +#define RDS_MEM_MAX_THRESH_PARSER (100) + +enum fm_host_rds_data_enum { + HOST_RDS_DATA_BLKTYPE_POSI = 0, + HOST_RDS_DATA_ERRORS_POSI = 3, + HOST_RDS_DATA_AVAIL_MASK = (1 << 5) +}; + +#define HOST_RDS_BLOCK_SIZE 3 +#define HOST_RDS_BLOCK_FMT_LSB 0 +#define HOST_RDS_BLOCK_FMT_MSB 1 +#define HOST_RDS_BLOCK_FMT_STATUS 2 + +enum fm_demod_stat_mask_enum { + FM_DEMOD_BLEND_STEREO_MASK = (0x0001 << 4), + FM_DEMOD_IF_OOR_MASK = (0x0001 << 7) +}; + +enum fm_int_src_mask_enum { + INT_IFC_READY_MASK = (0x0001 << 0), + INT_AUDIO_PAU_MASK = (0x0001 << 3), + INT_RDS_BYTES_MASK = (0x0001 << 4), +}; + +enum fm_audio_gain_enum { + AUDIO_ATTENUATION_Max = 0, + AUDIO_ATTENUATION_42dB = 1, + AUDIO_ATTENUATION_39dB = 2, + AUDIO_ATTENUATION_36dB = 3, + AUDIO_ATTENUATION_33dB = 4, + AUDIO_ATTENUATION_30dB = 5, + AUDIO_ATTENUATION_27dB = 6, + AUDIO_ATTENUATION_24dB = 7, + AUDIO_ATTENUATION_21dB = 8, + AUDIO_ATTENUATION_18dB = 9, + AUDIO_ATTENUATION_15dB = 10, + AUDIO_ATTENUATION_12dB = 11, + AUDIO_ATTENUATION_9dB = 12, + AUDIO_ATTENUATION_6dB = 13, + AUDIO_ATTENUATION_3dB = 14, + AUDIO_ATTENUATION_0dB = 15 +}; + +/***************************************************************************/ + +typedef struct { + u16 muffle_coeffs; + u16 lpf_en; + u16 lpf_auto; + u16 lpf_bw; +} soft_muffle_conf; + +typedef struct { +#ifdef USE_NEW_SCAN + u16 weak_ifca_l; + u16 weak_ifca_m; + u16 weak_ifca_h; + u16 normal_ifca_l; + u16 normal_ifca_m; + u16 normal_ifca_h; + bool weak_sig; +#else + u16 ifca_l; + u16 ifca_m; + u16 ifca_h; +#endif +} search_config; + +#ifdef MONO_SWITCH_INTERF +typedef struct { + s16 lo; + s16 hi; +} interf_rssi_thres; + +typedef struct { + u16 lo; + u16 hi; +} interf_snr_thres; +#endif /* MONO_SWITCH_INTERF */ + +typedef struct { + u32 demod_conf_ini; + u16 rssi_adj_ini; + soft_muffle_conf soft_muffle_conf_ini; + u16 soft_mute_atten_max_ini; + u16 stereo_thres_ini; + u16 narrow_thres_ini; + u16 snr_adj_ini; + u16 snr_smooth_conf_ini; + u16 mute_coeffs_soft; + u16 mute_coeffs_dis; + u16 blend_coeffs_soft; + u16 blend_coeffs_switch; + u16 blend_coeffs_dis; + u16 rds_int_byte_count; + search_config search_conf; +#ifdef MONO_SWITCH_INTERF + interf_rssi_thres interf_rssi; + interf_snr_thres interf_snr; +#endif + u16 rds_error_limit; +} fm_conf_ini_values; + +/***************************************************************************/ + +typedef struct { + bool rds_rx_enabled; + bool fm_pwr_on; + bool rds_pwr_on; + bool force_mono; + bool use_switched_blend; + bool use_soft_mute; + bool mute_forced; + bool mute_audio; + bool search_down; + bool use_rbds; + bool save_eblks; + bool last_status_blend_stereo; + bool last_status_rds_sync; +#ifdef MONO_SWITCH_INTERF + bool force_mono_interf; + bool interf_checked; + bool mono_switched_interf; + TIME mono_interf_reset_time; +#endif /* MONO_SWITCH_INTERF */ + u8 tuner_mode; + u8 status; + u8 rds_mem_thresh; + u8 rssi; + u8 band; + u16 last_ifc; + u16 snr; + u16 rssi_limit_normal; + u16 rssi_limit_search; + u32 freq; + u16 flags; + u8 rds_unsync_uncorr_weight; + u8 rds_unsync_blk_cnt; + u16 rds_unsync_bit_cnt; +} fm_state_s; + +typedef enum { + TUNER_OFF, + TUNER_NOTTUNED, + TUNER_IDLE, + TUNER_PRESET, + TUNER_SEARCH +} fm_tuner_state; + +/***************************************************************************/ + +typedef struct { + fm_tuner_state tuner_state; + bool curr_search_down; + bool hit_band_limit; + bool tune_done; + u16 freq_step; + u32 band_limit_lo; + u32 band_limit_hi; +} fm_tuner_state_s; + +typedef enum { + RDS_RM_ALIGN_0 = 0, + RDS_RM_ALIGN_1 = 1, + RDS_RM_ALIGN_2 = 2, + RDS_RM_ALIGN_3 = 3, + RDS_RM_ALIGN_NONE = 4 +} fm_rds_rm_align_enum; + +#ifdef USE_RDS_HW_DECODER +typedef enum { + RDS_BLKTYPE_A = 0, + RDS_BLKTYPE_B = 1, + RDS_BLKTYPE_C = 2, + RDS_BLKTYPE_C2 = 3, + RDS_BLKTYPE_D = 4, + RDS_BLKTYPE_E = 5, + RDS_NUM_BLOCK_TYPES = 6 +} fm_rds_block_type_enum; + +typedef enum { + RDS_STATE_INIT, + RDS_STATE_HAVE_DATA, + RDS_STATE_PRE_SYNC, + RDS_STATE_FULL_SYNC +} fm_rds_state_enum; +#else +typedef enum { + RDS_BLKTYPE_A = 0, + RDS_BLKTYPE_B = 1, + RDS_BLKTYPE_C = 2, + RDS_BLKTYPE_D = 3, + RDS_BLKTYPE_E = 4, + RDS_NUM_BLOCK_TYPES = 5 +} fm_rds_block_type_enum; + +typedef enum { + RDS_STATE_FOUND_BL_A, + RDS_STATE_FOUND_BL_B, + RDS_STATE_FOUND_BL_C, + RDS_STATE_FOUND_BL_D, + RDS_STATE_HAVE_DATA, + RDS_STATE_PRE_SYNC, + RDS_STATE_FULL_SYNC, + RDS_STATE_INIT, +} fm_rds_state_enum; +#endif /*USE_RDS_HW_DECODER*/ + +typedef struct { + unsigned current_state :3; + u16 error_bits; + u8 error_blks; +} fm_rds_state_s; + +typedef struct rds_buf_conf { + u8 *base; + u16 index; + u16 outdex; + u16 size; +} rds_buf_conf; + + +/****************************************************************************/ + +typedef struct struct_fm_rx_setup { + u32 fm_freq_hz; + u32 fm_freq_khz; + u16 demod_if; + s16 lna_cdac; + u16 spur_ctrl; + s16 spur_freq; +} struct_fm_rx_setup; + +typedef struct struct_fm_lo_setup { + u32 rx_lo_req_freq; + u32 n_mmdiv; + u32 frac_b1; + u32 frac_b0; + u32 n_lodiv; +} struct_fm_lo_setup; + + typedef struct struct_fm_rx_tune_info { + struct_fm_rx_setup rx_setup; + struct_fm_lo_setup lo_setup; +} struct_fm_rx_tune_info; + +/**********************************************/ +/* FM low struct + **********************************************/ +typedef struct { + u32 lo; + u32 hi; +} fm_band_s; + +struct s610_low { + /* fm low level struct - start */ + fm_conf_ini_values fm_config; + fm_state_s fm_state; + fm_tuner_state_s fm_tuner_state; + fm_rds_state_s fm_rds_state; + + u8 *rds_buffer_mem; + rds_buf_conf rds_buffer_config; + rds_buf_conf *rds_buffer; + + struct_fm_rx_tune_info fm_tune_info; + + fm_band_s fm_bands[2]; + + u16 fm_freq_steps[3]; + +#ifdef USE_SPUR_CANCEL + u32 fm_spur[256]; +#endif +#ifdef USE_SPUR_CANCEL_TRF + u32 fm_spur_trf[256]; +#endif +#ifdef USE_S612_DUAL_CLOCKING + u32 fm_dual_clk[256]; +#endif + /* fm low level struct - end */ +}; + +typedef void fm_callback_t(unsigned long); +typedef struct timer_list fm_timer_t; +typedef void fm_linux_callback_t(u_long); + +#define fm_set_flag_bits(radio, value) \ + fm_set_flags(radio, radio->low->fm_state.flags | (value)) +#define fm_clear_flag_bits(radio, value) \ + fm_set_flags(radio, radio->low->fm_state.flags & ~(value)) +#define fm_get_band(radio) (radio->low->fm_state.band) +#define fm_get_freq_step(radio) (radio->low->fm_tuner_state.freq_step) + + +#endif /*FM_LOW_STRUC_H*/ diff --git a/drivers/media/radio/s610/fm_rds.c b/drivers/media/radio/s610/fm_rds.c new file mode 100644 index 000000000000..539dea571d48 --- /dev/null +++ b/drivers/media/radio/s610/fm_rds.c @@ -0,0 +1,1553 @@ + +#include "fm_low_struc.h" +#include "radio-s610.h" +#include "fm_rds.h" +extern struct s610_radio *gradio; + +#ifdef USE_RINGBUFF_API +void ringbuf_reset(struct ringbuf_t *rb) +{ + rb->head = rb->tail = rb->buf; +} + +int ringbuf_buffer_size(const struct ringbuf_t *rb) +{ + return rb->size; +} + +int ringbuf_capacity(const struct ringbuf_t *rb) +{ + return ringbuf_buffer_size(rb) - 1; +} + +static const u8 *ringbuf_end(const struct ringbuf_t *rb) +{ + return rb->buf + ringbuf_buffer_size(rb); +} + +int ringbuf_bytes_free(const struct ringbuf_t *rb) +{ + if (rb->head >= rb->tail) + return ringbuf_capacity(rb) - (rb->head - rb->tail); + else + return rb->tail - rb->head - 1; +} + +int ringbuf_bytes_used(const struct ringbuf_t *rb) +{ + return ringbuf_capacity(rb) - ringbuf_bytes_free(rb); +} + +int ringbuf_is_full(const struct ringbuf_t *rb) +{ + return ringbuf_bytes_free(rb) == 0; +} + +int ringbuf_is_empty(const struct ringbuf_t *rb) +{ + return ringbuf_bytes_free(rb) == ringbuf_capacity(rb); +} + +const void *ringbuf_tail(const struct ringbuf_t *rb) +{ + return rb->tail; +} + +const void *ringbuf_head(const struct ringbuf_t *rb) +{ + return rb->head; +} + +static u8 *ringbuf_nextp(struct ringbuf_t *rb, const u8 *p) +{ + /* + * The assert guarantees the expression (++p - rb->buf) is + * non-negative; therefore, the modulus operation is safe and + * portable. + */ + return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb)); +} + +void *ringbuf_memcpy_into(struct ringbuf_t *dst, const void *src, int count) +{ + const u8 *u8src = src; + const u8 *bufend = ringbuf_end(dst); + int overflow = count > ringbuf_bytes_free(dst); + int nread = 0; + int n = 0; + + while (nread != count) { + n = MIN(bufend - dst->head, count - nread); + memcpy(dst->head, u8src + nread, n); + dst->head += n; + nread += n; + + /* wrap? */ + if (dst->head == bufend) + dst->head = dst->buf; + } + + if (overflow) + dst->tail = ringbuf_nextp(dst, dst->head); + + return dst->head; +} + +void *ringbuf_memcpy_from(void *dst, struct ringbuf_t *src, int count) +{ + int n = 0; + int bytes_used = ringbuf_bytes_used(src); + u8 *u8dst = dst; + const u8 *bufend = ringbuf_end(src); + int nwritten = 0; + + if (count > bytes_used) + return 0; + + while (nwritten != count) { + n = MIN(bufend - src->tail, count - nwritten); + memcpy(u8dst + nwritten, src->tail, n); + src->tail += n; + nwritten += n; + + /* wrap ? */ + if (src->tail == bufend) + src->tail = src->buf; + } + + return src->tail; +} + +void * ringbuf_memcpy_remove(struct ringbuf_t *dst, int count) +{ + int n = 0; + int bytes_used = ringbuf_bytes_used(dst); + const u8 *bufend = ringbuf_end(dst); + int nwritten = 0; + unsigned char *cls_start; + + if (count > bytes_used) + return 0; + + while (nwritten != count) { + n = MIN(bufend - dst->head, count - nwritten); + cls_start = dst->head - n; + memset(cls_start, 0, n); + dst->head -= n; + nwritten += n; + + /* wrap ? */ + if (dst->head == bufend) + dst->head = dst->buf; + } + + return dst->head; +} +#endif /* USE_RINGBUFF_API */ + +#ifdef USE_RINGBUFF_API +void fm_rds_write_data(struct s610_radio *radio, + u16 rds_data, fm_rds_block_type_enum blk_type, + fm_host_rds_errors_enum errors) +{ + u8 buf_ptr[HOST_RDS_BLOCK_SIZE]; + u16 usage; + + if (ringbuf_is_full(&radio->rds_rb)) { + dev_info(radio->dev, "%s():>>>RB full! H[%ld]T[%ld]", + __func__, + (unsigned long) (radio->rds_rb.head - radio->rds_rb.buf), + (unsigned long) (radio->rds_rb.tail - radio->rds_rb.buf)); + + goto skip_into_buf; + } + + buf_ptr[HOST_RDS_BLOCK_FMT_LSB] = (u8)(rds_data & 0xff); + buf_ptr[HOST_RDS_BLOCK_FMT_MSB] = (u8)(rds_data >> 8); + buf_ptr[HOST_RDS_BLOCK_FMT_STATUS] = + (blk_type << HOST_RDS_DATA_BLKTYPE_POSI) + | (errors << HOST_RDS_DATA_ERRORS_POSI) + | HOST_RDS_DATA_AVAIL_MASK; + + if (!ringbuf_memcpy_into(&radio->rds_rb, buf_ptr, HOST_RDS_BLOCK_SIZE)) { + usage = ringbuf_bytes_used(&radio->rds_rb); + dev_info(radio->dev, + "%s():>>>RB memcpy into fail! usage:%04d", + __func__, ringbuf_bytes_used(&radio->rds_rb)); + if (!usage) + return; + } + +skip_into_buf: + usage = ringbuf_bytes_used(&radio->rds_rb); + + if (usage >= HOST_RDS_BLOCK_SIZE) + radio->low->fm_state.status |= STATUS_MASK_RDS_AVA; + + if (radio->low->fm_state.rds_mem_thresh != 0) { + if (usage >= (radio->low->fm_state.rds_mem_thresh+(HOST_RDS_BLOCK_SIZE*4))) { + if (atomic_read(&radio->is_rds_new)) + return; + + fm_set_flag_bits(radio, FLAG_BUF_FUL); + atomic_set(&radio->is_rds_new, 1); + wake_up_interruptible(&radio->core->rds_read_queue); + radio->rds_n_count++; + + if (!(radio->rds_n_count%200)) { + fm_update_rssi_work(radio); + dev_info(radio->dev, + ">>>[FM] RSSI[%03d] NCOUNT[%08d] FIFO_ERR[%08d] USAGE[%04d] SYNCLOSS[%08d]", + radio->low->fm_state.rssi, radio->rds_n_count, radio->rds_fifo_err_cnt, + usage, radio->rds_sync_loss_cnt); + } + } + } +} +#else /* USE_RINGBUFF_API */ +void fm_rds_write_data(struct s610_radio *radio, u16 rds_data, + fm_rds_block_type_enum blk_type, fm_host_rds_errors_enum errors) +{ + u8 *buf_ptr; + u16 usage; + u16 capa; + + capa = radio->low->rds_buffer->size; + if (radio->low->rds_buffer->outdex + > radio->low->rds_buffer->index) + usage = radio->low->rds_buffer->size + - radio->low->rds_buffer->outdex + + radio->low->rds_buffer->index; + else + usage = radio->low->rds_buffer->index + - radio->low->rds_buffer->outdex; + + if ((capa - usage) >= (HOST_RDS_BLOCK_SIZE * 4)) { + buf_ptr = radio->low->rds_buffer->base + + radio->low->rds_buffer->index; + + buf_ptr[HOST_RDS_BLOCK_FMT_LSB] = (u8)(rds_data & 0xff); + buf_ptr[HOST_RDS_BLOCK_FMT_MSB] = (u8) (rds_data >> 8); + buf_ptr[HOST_RDS_BLOCK_FMT_STATUS] = (blk_type + << HOST_RDS_DATA_BLKTYPE_POSI) + | (errors << HOST_RDS_DATA_ERRORS_POSI) + | HOST_RDS_DATA_AVAIL_MASK; + + /* Advances the buffer's index */ + radio->low->rds_buffer->index + += HOST_RDS_BLOCK_SIZE; + + /* Check if the buffer's index wraps */ + if (radio->low->rds_buffer->index >= + radio->low->rds_buffer->size) { + radio->low->rds_buffer->index -= + radio->low->rds_buffer->size; + } + + if (usage >= HOST_RDS_BLOCK_SIZE) + radio->low->fm_state.status |= STATUS_MASK_RDS_AVA; + + } + + if (radio->low->fm_state.rds_mem_thresh != 0) { + if (usage >= (radio->low->fm_state.rds_mem_thresh+(HOST_RDS_BLOCK_SIZE*4) + /** HOST_RDS_BLOCK_SIZE*/)) { + if (atomic_read(&radio->is_rds_new)) + return; + + fm_set_flag_bits(radio, FLAG_BUF_FUL); + atomic_set(&radio->is_rds_new, 1); + wake_up_interruptible(&radio->core->rds_read_queue); + radio->rds_n_count++; + + if (!(radio->rds_n_count%200)) { + fm_update_rssi_work(radio); + dev_info(radio->dev, + ">>>[FM] RSSI[%03d] NCOUNT[%08d] FIFO_ERR[%08d] USAGE[%04d] SYNCLOSS[%08d]", + radio->low->fm_state.rssi, radio->rds_n_count, radio->rds_fifo_err_cnt, + usage, radio->rds_sync_loss_cnt); + } + } + } +} +#endif /* USE_RINGBUFF_API */ + +#ifdef USE_RDS_BLOCK_SEQ_CORRECT +#ifdef USE_RINGBUFF_API +void fm_rds_write_data_remove(struct s610_radio *radio, + fm_rds_rm_align_enum removeblock) +{ + unsigned long pre_head, cur_head; + pre_head = (unsigned long) radio->rds_rb.head; + ringbuf_memcpy_remove(&radio->rds_rb, + (int)removeblock*HOST_RDS_BLOCK_SIZE); + cur_head = (unsigned long) radio->rds_rb.head; + dev_info(radio->dev, ">>> pre-head :%08lX, cur-head :%08lX\n", + pre_head, cur_head); +} +#else +void fm_rds_write_data_remove(struct s610_radio *radio, + fm_rds_rm_align_enum removeblock) +{ + int i; + u8 *buf_ptr; + + for (i = 0; i < removeblock*HOST_RDS_BLOCK_SIZE; i++) { + buf_ptr = radio->low->rds_buffer->base + + radio->low->rds_buffer->index; + buf_ptr[0] = 0; + if (radio->low->rds_buffer->index == 0) + radio->low->rds_buffer->index = radio->low->rds_buffer->size; + radio->low->rds_buffer->index--; + } + RDSEBUG(radio, "%s():<<low->rds_buffer->index); + +} +#endif /*USE_RINGBUFF_API*/ +#endif /* USE_RDS_BLOCK_SEQ_CORRECT */ + +#ifdef USE_RINGBUFF_API +int fm_read_rds_data(struct s610_radio *radio, u8 *buffer, int size, + u16 *blocks) +{ + u16 rsize = size; + + if (ringbuf_is_empty(&radio->rds_rb)) { + dev_info(radio->dev, + "%s():>>>RB empty!! H[%04ld]T[%04ld]", + __func__, + (unsigned long) (radio->rds_rb.head - radio->rds_rb.buf), + (unsigned long) (radio->rds_rb.tail - radio->rds_rb.buf)); + + return 0; + } + radio->rb_used = ringbuf_bytes_used(&radio->rds_rb); + if (!ringbuf_memcpy_from(buffer, &radio->rds_rb, rsize)) { + dev_info(radio->dev, + "%s():>>>RB memcpy from fail! H[%04ld]T[%04ld]", + __func__, + (unsigned long) (radio->rds_rb.head - radio->rds_rb.buf), + (unsigned long) (radio->rds_rb.tail - radio->rds_rb.buf)); + + /* ringbuff reset */ + ringbuf_reset(&radio->rds_rb); + if (blocks) + blocks = 0; + return 0; + } + + if (blocks) + *blocks = rsize / HOST_RDS_BLOCK_SIZE; + + /* Update RDS flags */ + if ((rsize / HOST_RDS_BLOCK_SIZE) < radio->low->fm_state.rds_mem_thresh) + fm_clear_flag_bits(radio, FLAG_BUF_FUL); + + RDSEBUG(radio, + "%s():>>>RB1 H[%04ld]T[%04ld]", + __func__, + (unsigned long) (radio->rds_rb.head - radio->rds_rb.buf), + (unsigned long) (radio->rds_rb.tail - radio->rds_rb.buf)); + + return rsize; +} +#else /* USE_RINGBUFF_API */ +u16 fm_rds_get_avail_bytes(struct s610_radio *radio) +{ + u16 avail_bytes; + + if (radio->low->rds_buffer->outdex > + radio->low->rds_buffer->index) + avail_bytes = (radio->low->rds_buffer->size + - radio->low->rds_buffer->outdex + + radio->low->rds_buffer->index); + else + avail_bytes = (radio->low->rds_buffer->index + - radio->low->rds_buffer->outdex); + + return avail_bytes; +} + + +int fm_read_rds_data(struct s610_radio *radio, u8 *buffer, int size, + u16 *blocks) +{ + u16 avail_bytes; + s16 avail_blocks; + s16 orig_avail; + u8 *buf_ptr; + + if (radio->low->rds_buffer == NULL) { + size = 0; + if (blocks) + *blocks = 0; + return FALSE; + } + + orig_avail = avail_bytes = fm_rds_get_avail_bytes(radio); + + if (avail_bytes > size) + avail_bytes = size; + + avail_blocks = avail_bytes / HOST_RDS_BLOCK_SIZE; + avail_bytes = avail_blocks * HOST_RDS_BLOCK_SIZE; + + if (avail_bytes == 0) { + size = 0; + if (blocks) + *blocks = 0; + return FALSE; + } + + buf_ptr = radio->low->rds_buffer->base + + radio->low->rds_buffer->outdex; + (void) memcpy(buffer, buf_ptr, avail_bytes); + + /* advances the buffer's outdex */ + radio->low->rds_buffer->outdex += avail_bytes; + + /* Check if the buffer's outdex wraps */ + if (radio->low->rds_buffer->outdex >= radio->low->rds_buffer->size) + radio->low->rds_buffer->outdex -= radio->low->rds_buffer->size; + + if (orig_avail == avail_bytes) { + buffer[(avail_blocks - 1) * HOST_RDS_BLOCK_SIZE + + HOST_RDS_BLOCK_FMT_STATUS] &= + ~HOST_RDS_DATA_AVAIL_MASK; + radio->low->fm_state.status &= ~STATUS_MASK_RDS_AVA; + } + + size = avail_bytes; /* number of bytes read */ + + if (blocks) + *blocks = avail_bytes / HOST_RDS_BLOCK_SIZE; + + /* Update RDS flags */ + if ((avail_bytes / HOST_RDS_BLOCK_SIZE) + < radio->low->fm_state.rds_mem_thresh) + fm_clear_flag_bits(radio, FLAG_BUF_FUL); + + return size; +} +#endif /* USE_RINGBUFF_API */ + +#ifdef USE_RDS_HW_DECODER +void fm_rds_change_state(struct s610_radio *radio, + fm_rds_state_enum new_state) +{ + fm_rds_state_enum old_state = + (fm_rds_state_enum) radio->low->fm_rds_state.current_state; + + radio->low->fm_rds_state.current_state = new_state; + + if ((old_state == RDS_STATE_FULL_SYNC) + && (new_state == RDS_STATE_HAVE_DATA)) { + fm_update_rds_sync_status(radio, FALSE); /* unsynced */ + } else if ((old_state != RDS_STATE_FULL_SYNC) + && (new_state == RDS_STATE_FULL_SYNC)) { + fm_update_rds_sync_status(radio, TRUE); /* synced */ + } +} +#else /*USE_RDS_HW_DECODER*/ +void fm_rds_change_state(struct s610_radio *radio, + fm_rds_state_enum new_state) +{ + radio->low->fm_rds_state.current_state = new_state; +} + +fm_rds_state_enum fm_rds_get_state(struct s610_radio *radio) +{ + return radio->low->fm_rds_state.current_state; +} +#endif /*USE_RDS_HW_DECODER*/ + +void fm_rds_update_error_status(struct s610_radio *radio, u16 errors) +{ + if (errors == 0) { + radio->low->fm_rds_state.error_bits = 0; + radio->low->fm_rds_state.error_blks = 0; + } else { + radio->low->fm_rds_state.error_bits += errors; + radio->low->fm_rds_state.error_blks++; + } + + if (radio->low->fm_rds_state.error_blks + >= radio->low->fm_state.rds_unsync_blk_cnt) { + if (radio->low->fm_rds_state.error_bits + >= radio->low->fm_state.rds_unsync_bit_cnt) { + /* sync-loss */ + fm_rds_change_state(radio, RDS_STATE_HAVE_DATA); + RDSEBUG(radio, "%s() >>>>> RDS sync-loss[%08d]!!!!!", + __func__, radio->rds_sync_loss_cnt); + +#ifdef USE_RDS_HW_DECODER + fm_rds_change_state(radio, RDS_STATE_HAVE_DATA); +#else + if (!radio->rds_sync_loss_cnt) { +#ifdef USE_RINGBUFF_API + ringbuf_reset(&radio->rds_rb); +#else + radio->low->rds_buffer->index = radio->low->rds_buffer->outdex = 0; +#endif + } else { + /*remove data*/ + fm_rds_write_data_remove(radio, fm_rds_get_state(radio)); + } + fm_rds_change_state(radio, RDS_STATE_INIT); +#endif /*USE_RDS_HW_DECODER*/ + } + radio->low->fm_rds_state.error_bits = 0; + radio->low->fm_rds_state.error_blks = 0; + radio->rds_sync_loss_cnt++; + } +} + +static fm_host_rds_errors_enum fm_rds_process_block( + struct s610_radio *radio, + u16 data, fm_rds_block_type_enum block_type, + u16 err_count) +{ + fm_host_rds_errors_enum error_type; + struct fm_rds_parser_info *pi; + + if (radio->rds_parser_enable) + pi = &(radio->pi); + + if (err_count == 0) { + error_type = HOST_RDS_ERRS_NONE; + } else if ((err_count <= 2) + && (err_count + <= radio->low->fm_config.rds_error_limit)) { + error_type = HOST_RDS_ERRS_2CORR; + } else if ((err_count <= 5) + && (err_count + <= radio->low->fm_config.rds_error_limit)) { + error_type = HOST_RDS_ERRS_5CORR; + } else { + error_type = HOST_RDS_ERRS_UNCORR; + } + + /* Write the data into the buffer */ + if ((block_type != RDS_BLKTYPE_E) + || (radio->low->fm_state.save_eblks)) { + if (radio->rds_parser_enable) { + fm_rds_parser(pi, data, block_type, error_type); + fm_rds_write_data_pi(radio, pi); + } else { + fm_rds_write_data(radio, data, block_type, error_type); + } + } + + return error_type; +} + +#ifdef USE_RDS_BLOCK_SEQ_CORRECT +fm_rds_rm_align_enum fm_check_block_seq(fm_rds_block_type_enum pre_block_type, + fm_rds_block_type_enum curr_block_type) +{ + fm_rds_rm_align_enum ret = RDS_RM_ALIGN_NONE; + + if ((pre_block_type == RDS_BLKTYPE_A) && (curr_block_type != RDS_BLKTYPE_B)) { + ret = RDS_RM_ALIGN_1; + } + else if ((pre_block_type == RDS_BLKTYPE_B) && (curr_block_type != RDS_BLKTYPE_C)) { + ret = RDS_RM_ALIGN_2; + } + else if ((pre_block_type == RDS_BLKTYPE_C) && (curr_block_type != RDS_BLKTYPE_D)) { + ret = RDS_RM_ALIGN_3; + } + else if ((pre_block_type == RDS_BLKTYPE_D) && (curr_block_type != RDS_BLKTYPE_A)) { + ret = RDS_RM_ALIGN_0; + } + return ret; +} +#endif /* USE_RDS_BLOCK_SEQ_CORRECT */ + +#ifdef USE_RDS_HW_DECODER +void fm_process_rds_data(struct s610_radio *radio) +{ + u32 fifo_data; + u16 i; + u16 avail_blocks; + u16 data; + u8 status; + u16 err_count; + fm_rds_block_type_enum block_type; +#ifdef USE_RDS_BLOCK_SEQ_CORRECT + fm_rds_rm_align_enum rm_blk = RDS_RM_ALIGN_NONE; +#endif /* USE_RDS_BLOCK_SEQ_CORRECT */ + + API_ENTRY(radio); + + if (!radio->low->fm_state.rds_rx_enabled) + return; + + avail_blocks = RDS_MEM_MAX_THRESH/4; + + for (i = 0; i < avail_blocks; i++) { + /* Fetch the RDS word data. */ + atomic_set(&radio->is_rds_doing, 1); + fifo_data = fmspeedy_get_reg_work(0xFFF3C0); + radio->rds_fifo_rd_cnt++; + data = (u16)((fifo_data >> 16) & 0xFFFF); + status = (u8)((fifo_data >> 8) & 0xFF); + block_type = + (fm_rds_block_type_enum) ((status & RDS_BLK_TYPE_MASK) + >> RDS_BLK_TYPE_SHIFT); + err_count = (status & RDS_ERR_CNT_MASK); + atomic_set(&radio->is_rds_doing, 0); + + switch (radio->low->fm_rds_state.current_state) { + case RDS_STATE_INIT: + APIEBUG(radio, "RDS_STATE_INIT"); + fm_rds_change_state(radio, RDS_STATE_HAVE_DATA); + case RDS_STATE_HAVE_DATA: + APIEBUG(radio, "RDS_STATE_HAVE_DATA"); + if ((block_type == RDS_BLKTYPE_A) + && (err_count == 0)) { +#ifdef USE_RDS_BLOCK_SEQ_CORRECT + radio->block_seq = RDS_BLKTYPE_A; +#endif /*USE_RDS_BLOCK_SEQ_CORRECT */ + /* Move to full sync */ + fm_rds_change_state(radio, + RDS_STATE_FULL_SYNC); + fm_rds_process_block(radio, + data, block_type, err_count); + } + break; + case RDS_STATE_PRE_SYNC: + break; + case RDS_STATE_FULL_SYNC: + APIEBUG(radio, "RDS_STATE_FULL_SYNC"); +#ifdef USE_RDS_BLOCK_SEQ_CORRECT + rm_blk = fm_check_block_seq(radio->block_seq, block_type); + if (rm_blk < RDS_RM_ALIGN_NONE) { + RDSEBUG(radio, + "pre block[%02d],curr block[%02d],err count[%08d]", + radio->block_seq, block_type, radio->rds_fifo_err_cnt); + if (rm_blk != RDS_RM_ALIGN_0) { + fm_rds_write_data_remove(radio, rm_blk); + radio->block_seq = RDS_BLKTYPE_D; + } + fm_rds_change_state(radio, RDS_STATE_HAVE_DATA); + radio->rds_fifo_err_cnt++; + break; + } + radio->block_seq = block_type; +#endif /* USE_RDS_BLOCK_SEQ_CORRECT */ + if (fm_rds_process_block(radio, + data, block_type, err_count) + == HOST_RDS_ERRS_UNCORR) { + fm_rds_update_error_status(radio, + radio->low->fm_state.rds_unsync_uncorr_weight); + } else { + fm_rds_update_error_status(radio, err_count); + } + break; + } + } + + API_EXIT(radio); +} +#endif /* USE_RDS_HW_DECODER */ +void find_pi_data(struct fm_rds_parser_info *pi, u16 info) +{ + + pi->pi_buf[pi->pi_idx % 2] = info; + + if (!(++pi->pi_idx % 2)) { + if (pi->pi_buf[0] == pi->pi_buf[1]) { + pi->rds_event |= RDS_EVENT_PI_MASK; + RDSEBUG(gradio, "[RDS] PI : 0x%x\n", pi->pi_buf[0]); + } + } +} + +void find_ecc_data(struct fm_rds_parser_info *pi, u16 info) +{ + + pi->ecc_buf[pi->ecc_idx % 2] = info & 0xFF; + + if (!(++pi->ecc_idx % 2)) { + if (pi->ecc_buf[0] == pi->ecc_buf[1]) { + pi->rds_event |= RDS_EVENT_ECC_MASK; + RDSEBUG(gradio, "[RDS] ECC : %d\n", pi->ecc_buf[0]); + } + } +} + +void store_ps_data(struct fm_rds_parser_info *pi, + u16 info, u8 err_cnt) +{ + char a, b; + u32 i = pi->ps_idx % 3; + u8 seg = pi->ps_segment; + + if (pi->drop_blk) + return; + + a = (info >> 8) & 0xff; + b = info & 0xff; + + pi->ps_buf[i][seg * 2] = a; + pi->ps_buf[i][seg * 2 + 1] = b; + pi->ps_err[i][seg] = err_cnt; + /*RDSEBUG(gradio, + "[RDS] PS: [%c][%c] [%d][%d], modulo idx=%d, seg=%d\n", + a, b, (int)a, (int)b, i, seg);*/ +} + +void validate_ps_data(struct fm_rds_parser_info *pi) +{ + u32 i; + bool match = true; + + for (i = 0; i < pi->ps_len / 2; i++) { + if (pi->ps_err[pi->ps_idx % 3][i] > 0) + break; + } + + if (i == pi->ps_len / 2) { + memcpy(pi->ps_candidate, + pi->ps_buf[pi->ps_idx % 3], + pi->ps_len); + memset(pi->ps_err[pi->ps_idx % 3], + 0xFF, MAX_PS / 2); + pi->ps_candidate[pi->ps_len] = 0; + pi->rds_event |= RDS_EVENT_PS_MASK; + pi->ps_idx++; + RDSEBUG(gradio, + "[RDS] ### PS candidate[i]: %s\n", + pi->ps_candidate); + } else { + if (++pi->ps_idx >= 3) { + for (i = 0; i < pi->ps_len; i++) { + if (pi->ps_buf[0][i] == pi->ps_buf[1][i]) + pi->ps_candidate[i] = pi->ps_buf[0][i]; + else if (pi->ps_buf[0][i] == pi->ps_buf[2][i]) + pi->ps_candidate[i] = pi->ps_buf[0][i]; + else if (pi->ps_buf[1][i] == pi->ps_buf[2][i]) + pi->ps_candidate[i] = pi->ps_buf[1][i]; + else + match = false; + } + + if (match) { + pi->ps_candidate[pi->ps_len] = 0; + pi->rds_event |= RDS_EVENT_PS_MASK; + RDSEBUG(gradio, + "[RDS] ### PS candidate[m]: %s\n", + pi->ps_candidate); + } + } + } + + i = pi->ps_idx - 1; + pi->ps_buf[i % 3][pi->ps_len] = 0; + + for (i = 0; i < 3; i++) + RDSEBUG(gradio, + "[RDS] ### PS received[%d]: %s\n", + i, pi->ps_buf[i]); +} + +void store_rt_data(struct fm_rds_parser_info *pi, u16 info, u8 blk_type, u8 err_cnt) +{ + char a, b; + u32 i = pi->rt_idx % 3; + u8 seg = pi->rt_segment; + + if (pi->drop_blk) + return; + + a = (info >> 8) & 0xff; + b = info & 0xff; + + switch (blk_type) { + case RDS_BLKTYPE_C: + pi->rt_buf[i][seg * 4] = a; + pi->rt_buf[i][seg * 4 + 1] = b; + pi->rt_err[i][seg * 2] = err_cnt; + /*RDSEBUG(gradio, + "[RDS] RT_A(C): [%c][%c] [%d][%d], modulo idx=%d, seg=%d\n", + a, b, (int)a, (int)b, i, seg);*/ + break; + + case RDS_BLKTYPE_D: + if (pi->grp == RDS_GRPTYPE_2A) { + pi->rt_buf[i][seg * 4 + 2] = a; + pi->rt_buf[i][seg * 4 + 3] = b; + pi->rt_err[i][seg * 2 + 1] = err_cnt; + /*RDSEBUG(gradio, + "[RDS] RT_A(D): [%c][%c] [%d][%d], modulo idx=%d, seg=%d\n", + a, b, (int)a, (int)b, i, seg);*/ + } else if (pi->grp == RDS_GRPTYPE_2B) { + pi->rt_buf[i][seg * 2] = a; + pi->rt_buf[i][seg * 2 + 1] = b; + pi->rt_err[i][seg] = err_cnt; + /*RDSEBUG(gradio, + "[RDS] RT_B(C): [%c][%c] [%d][%d], modulo idx=%d, seg=%d\n", + a, b, (int)a, (int)b, i, seg);*/ + } + default: + break; + } +} + +void validate_rt_data(struct fm_rds_parser_info *pi) +{ + u32 i; + bool match = true; + + for (i = 0; i < pi->rt_len / 2; i++) { + if (pi->rt_err[pi->rt_idx % 3][i] > 0) + break; + } + + if (i == pi->rt_len / 2) { + memcpy(pi->rt_candidate, pi->rt_buf[pi->rt_idx % 3], pi->rt_len); + memset(pi->rt_err[pi->rt_idx % 3], 0xFF, MAX_RT / 2); + pi->rt_candidate[pi->rt_len] = 0; + + if (strlen(pi->rt_candidate) == pi->rt_len) { + pi->rds_event |= RDS_EVENT_RT_MASK; + pi->rt_validated = 1; + pi->rt_idx++; + RDSEBUG(gradio, + "[RDS] ### RT candidate[i]: %s, %d, %d\n", + pi->rt_candidate, (int)strlen(pi->rt_candidate), pi->rt_len); + } + } else { + if (++pi->rt_idx >= 3) { + for (i = 0; i < pi->rt_len; i++) { + if (pi->rt_buf[0][i] == pi->rt_buf[1][i]) + pi->rt_candidate[i] = pi->rt_buf[0][i]; + else if (pi->rt_buf[0][i] == pi->rt_buf[2][i]) + pi->rt_candidate[i] = pi->rt_buf[0][i]; + else if (pi->rt_buf[1][i] == pi->rt_buf[2][i]) + pi->rt_candidate[i] = pi->rt_buf[1][i]; + else + match = false; + } + + if (match) { + pi->rt_candidate[pi->rt_len] = 0; + + if (strlen(pi->rt_candidate) == pi->rt_len) { + pi->rds_event |= RDS_EVENT_RT_MASK; + pi->rt_validated = 1; + RDSEBUG(gradio, + "[RDS] ### RT candidate[m]: %s, %d, %d\n", + pi->rt_candidate, (int)strlen(pi->rt_candidate), pi->rt_len); + } + } + } + } + + i = pi->rt_idx - 1; + pi->rt_buf[i % 3][pi->rt_len] = 0; + for (i = 0; i < 3; i++) + RDSEBUG(gradio, + "[RDS] ### RT received[%d]: %s\n", + i, pi->rt_buf[i]); +} + +void reset_rtp_data(struct fm_rds_parser_info *pi) +{ + memset(&(pi->rtp_data), 0x0, sizeof(struct rtp_info)); + memset(pi->rtp_raw_data, 0x0, sizeof(u16) * 3); +} + +void validate_rtp_data(struct fm_rds_parser_info *pi) +{ + u8 i; + static u16 rtp_validated; + struct rtp_tag_info tag[2]; + + if (!pi->rtp_data.running) { + RDSEBUG(gradio, "[RDS] RTP running is stopped\n"); + return; + } + + tag[0].content_type = ((pi->rtp_raw_data[RDS_BLKTYPE_B - 1] & 0x0007) << 3) | + ((pi->rtp_raw_data[RDS_BLKTYPE_C - 1] & 0xE000) >> 13); + tag[0].start_pos = (pi->rtp_raw_data[RDS_BLKTYPE_C - 1] & 0x1F80) >> 7; + tag[0].len = (pi->rtp_raw_data[RDS_BLKTYPE_C - 1] & 0x007E) >> 1; + + tag[1].content_type = ((pi->rtp_raw_data[RDS_BLKTYPE_C - 1] & 0x0001) << 5) | + ((pi->rtp_raw_data[RDS_BLKTYPE_D - 1] & 0xF800) >> 11); + tag[1].start_pos = (pi->rtp_raw_data[RDS_BLKTYPE_D - 1] & 0x07E0) >> 5; + tag[1].len = pi->rtp_raw_data[RDS_BLKTYPE_D - 1] & 0x001F; + + RDSEBUG(gradio, "[RDS] RTP tag[0]:[%d,%d,%d]\n", tag[0].content_type, tag[0].start_pos, tag[0].len); + RDSEBUG(gradio, "[RDS] RTP tag[1]:[%d,%d,%d]\n", tag[1].content_type, tag[1].start_pos, tag[1].len); + + /* Check overlap */ + if (((tag[1].content_type != 0) && (tag[0].start_pos < tag[1].start_pos) && ((tag[0].start_pos + tag[0].len) >= tag[1].start_pos)) || + ((tag[0].content_type != 0) && (tag[1].start_pos < tag[0].start_pos) && ((tag[1].start_pos + tag[1].len) >= tag[0].start_pos))) { + RDSEBUG(gradio, "[RDS] RTP tag[0] & tag[1] are overlapped.\n"); + return; + } + + for (i = 0; i < MAX_RTP_TAG; i++) { + if (tag[i].content_type == pi->rtp_data.tag[i].content_type && + tag[i].start_pos == pi->rtp_data.tag[i].start_pos && + tag[i].len == pi->rtp_data.tag[i].len) { + rtp_validated++; + RDSEBUG(gradio, "[RDS] RTP tag validation check count:0x%x\n", (int)rtp_validated); + } else { + pi->rtp_data.tag[i].content_type = tag[i].content_type; + pi->rtp_data.tag[i].start_pos = tag[i].start_pos; + pi->rtp_data.tag[i].len = tag[i].len; + rtp_validated = 0; + } + } + + /* RT is ready to be displayed along with RTP data */ + if (pi->rt_validated && rtp_validated > 2) + pi->rds_event |= RDS_EVENT_RTP_MASK; +} + +void store_rtp_data(struct fm_rds_parser_info *pi, u16 info, u8 blk_type) +{ + u8 toggle; + + if (pi->drop_blk) { + return; + } + + if (pi->grp != pi->rtp_code_group) { + RDSEBUG(gradio, "[RDS] Received unexpected code group(0x%x), expected=0x%x\n", + (int)pi->grp, (int)pi->rtp_code_group); + return; + } + + switch (blk_type) { + case RDS_BLKTYPE_B: + toggle = (info & 0x010) >> 4; + if (toggle != pi->rtp_data.toggle) { + reset_rtp_data(pi); + pi->rtp_data.toggle = toggle; + } + pi->rtp_data.running = (info & 0x0008) >> 3; + pi->rtp_raw_data[blk_type-1] = info; + RDSEBUG(gradio, "[RDS] Received RTP B block/0x%x Group\n", (int)pi->grp); + break; + case RDS_BLKTYPE_C: + pi->rtp_raw_data[blk_type-1] = info; + RDSEBUG(gradio, "[RDS] Received RTP C block/0x%x Group\n", (int)pi->grp); + break; + case RDS_BLKTYPE_D: + pi->rtp_raw_data[blk_type-1] = info; + RDSEBUG(gradio, "[RDS] Received RTP D block/0x%x Group\n", (int)pi->grp); + validate_rtp_data(pi); + break; + default: + break; + } +} + +void find_rtp_data(struct fm_rds_parser_info *pi, u16 info, u8 blk_type) +{ + if (pi->drop_blk) + return; + + switch (blk_type) { + case RDS_BLKTYPE_B: + pi->rtp_code_group = info & 0x1f; + RDSEBUG(gradio, "[RDS] RTP code group:0x%x\n", (int)pi->rtp_code_group); + break; + case RDS_BLKTYPE_C: + /* Not support SCB/RTP template */ + RDSEBUG(gradio, "[RDS] RTP not support SCB/RTP template\n"); + break; + case RDS_BLKTYPE_D: + if (info != RDS_RTP_AID) { + RDSEBUG(gradio, "[RDS] Invalid RTP aid=0x%x\n", (int)info); + pi->rtp_code_group = 0; + } + break; + default: + break; + } +} + +void find_group_data(struct fm_rds_parser_info *pi, u16 info) +{ + u8 segment, rt_change; + + pi->grp = (info >> 11) & 0x1f; + + switch (pi->grp) { + case RDS_GRPTYPE_0A: + case RDS_GRPTYPE_0B: + segment = info & 0x3; + + if (!segment && pi->ps_segment != 0xFF) + validate_ps_data(pi); + + pi->ps_segment = segment; + + if (pi->ps_len < (segment + 1) * 2) + pi->ps_len = (segment + 1) * 2; + + /*RDSEBUG(gradio, + "[RDS] PS: seg=%d, len=%d\n", + segment, pi->ps_len);*/ + break; + + case RDS_GRPTYPE_2A: + case RDS_GRPTYPE_2B: + segment = info & 0xF; + rt_change = (info & 0x10) >> 4; + + RDSEBUG(gradio, + "[RDS] segment=%d, pi->rt_segment=%d, pi->rt_change=%d, rt_change=%d\n", + segment, pi->rt_segment, pi->rt_change, rt_change); + + if ((!segment && pi->rt_segment != 0xFF) + || (pi->rt_change != 0xFF && pi->rt_change != rt_change)) { + validate_rt_data(pi); + + if (pi->rt_change != 0xFF && pi->rt_change != rt_change) { + pi->rt_len = 0; + pi->rt_idx = 0; + memset(pi->rt_buf, 0, 3 * (MAX_RT + 1)); + memset(pi->rt_err, 0xFF, 3 * MAX_RT / 2); + pi->rt_validated = 0; + } + } + + pi->rt_segment = segment; + pi->rt_change = rt_change; + + if (pi->grp == RDS_GRPTYPE_2A) { + if (pi->rt_len < (segment + 1) * 4) + pi->rt_len = (segment + 1) * 4; + } else { + if (pi->rt_len < (segment + 1) * 2) + pi->rt_len = (segment + 1) * 2; + } + + RDSEBUG(gradio, + "[RDS] RT: seg=%d, tc=%d, len=%d\n", + segment, rt_change, pi->rt_len); + break; + + case RDS_GRPTYPE_3A: + find_rtp_data(pi, info, RDS_BLKTYPE_B); + break; + + case RDS_GRPTYPE_5A: + case RDS_GRPTYPE_6A: + case RDS_GRPTYPE_7A: + case RDS_GRPTYPE_8A: + case RDS_GRPTYPE_9A: + case RDS_GRPTYPE_11A: + case RDS_GRPTYPE_12A: + case RDS_GRPTYPE_13A: + store_rtp_data(pi, info, RDS_BLKTYPE_B); + break; + + default: + break; + } +} + +void find_af_data(struct fm_rds_parser_info *pi, u16 info) +{ + + pi->af_buf[pi->af_idx % 2] = info; + + if (!(++pi->af_idx % 2)) { + if (pi->af_buf[0] == pi->af_buf[1]) + pi->rds_event |= RDS_EVENT_AF_MASK; + } +} + +void fm_rds_parser_reset(struct fm_rds_parser_info *pi) +{ + /* reset rds parser data structure */ + memset(pi, 0x0, sizeof(struct fm_rds_parser_info)); + + pi->ps_segment = 0xFF; + pi->rt_segment = 0xFF; + pi->rt_change = 0xFF; + pi->rtp_data.toggle = 0xFF; + memset(pi->rt_err, 0xFF, 3 * MAX_RT / 2); + memset(pi->ps_err, 0xFF, 3 * MAX_PS / 2); +} + +void fm_rds_parser(struct fm_rds_parser_info *pi, u16 info, u8 blk_type, u8 err_cnt) +{ + + switch (blk_type) { + case RDS_BLKTYPE_A: + find_pi_data(pi, info); + break; + + case RDS_BLKTYPE_B: + if (err_cnt > 2) { + pi->grp = RDS_GRPTYPE_NONE; + pi->drop_blk = true; + break; + } else { + pi->drop_blk = false; + } + + find_group_data(pi, info); + break; + + case RDS_BLKTYPE_C: + if (err_cnt > 5) + return; + + switch (pi->grp) { + case RDS_GRPTYPE_0A: + find_af_data(pi, info); + break; + + case RDS_GRPTYPE_0B: + find_pi_data(pi, info); + break; + + case RDS_GRPTYPE_1A: + find_ecc_data(pi, info); + break; + + case RDS_GRPTYPE_2A: + store_rt_data(pi, info, blk_type, err_cnt); + break; + + case RDS_GRPTYPE_2B: + find_pi_data(pi, info); + break; + + case RDS_GRPTYPE_3A: + find_rtp_data(pi, info, blk_type); + break; + + case RDS_GRPTYPE_5A: + case RDS_GRPTYPE_6A: + case RDS_GRPTYPE_7A: + case RDS_GRPTYPE_8A: + case RDS_GRPTYPE_9A: + case RDS_GRPTYPE_11A: + case RDS_GRPTYPE_12A: + case RDS_GRPTYPE_13A: + store_rtp_data(pi, info, blk_type); + break; + + default: + break; + } + break; + + case RDS_BLKTYPE_D: + if (err_cnt > 5) + return; + + switch (pi->grp) { + case RDS_GRPTYPE_0A: + case RDS_GRPTYPE_0B: + store_ps_data(pi, info, err_cnt); + break; + + case RDS_GRPTYPE_2A: + case RDS_GRPTYPE_2B: + store_rt_data(pi, info, blk_type, err_cnt); + break; + + case RDS_GRPTYPE_3A: + find_rtp_data(pi, info, blk_type); + break; + + case RDS_GRPTYPE_5A: + case RDS_GRPTYPE_6A: + case RDS_GRPTYPE_7A: + case RDS_GRPTYPE_8A: + case RDS_GRPTYPE_9A: + case RDS_GRPTYPE_11A: + case RDS_GRPTYPE_12A: + case RDS_GRPTYPE_13A: + store_rtp_data(pi, info, blk_type); + break; + + default: + break; + } + break; + } +} + +void fm_rds_write_data_pi(struct s610_radio *radio, + struct fm_rds_parser_info *pi) +{ + u8 buf_ptr[RDS_MEM_MAX_THRESH_PARSER]; + u8 i, str_len; + int total_size = 0; + u16 usage; + +#ifdef USE_RINGBUFF_API + if (ringbuf_bytes_free(&radio->rds_rb) < RDS_MEM_MAX_THRESH_PARSER) { + dev_info(radio->dev, ">>> ringbuff not free, wait.."); + /*ringbuf_reset(&radio->rds_rb);*/ + return; + } +#else + dev_info(radio->dev, ">>> ringbuff API not used, fail.."); + return; +#endif /* USE_RINGBUFF_API */ + + memset(buf_ptr, 0, RDS_MEM_MAX_THRESH_PARSER); + + while (pi->rds_event) { + RDSEBUG(radio, + "%s() rds_event[%04X]\n", __func__, pi->rds_event); + + if (pi->rds_event & RDS_EVENT_PI_MASK) { + pi->rds_event &= ~RDS_EVENT_PI_MASK; + buf_ptr[total_size] = RDS_EVENT_PI; + buf_ptr[total_size+1] = 2; + buf_ptr[total_size+2] = (u8)(pi->pi_buf[0] & 0xFF); + buf_ptr[total_size+3] = (u8)(pi->pi_buf[0] >> 8); + RDSEBUG(radio, + "[RDS] Enqueue PI data[%02X:%02X:%02X:%02X]. total=%d\n", + buf_ptr[total_size],buf_ptr[total_size+1],buf_ptr[total_size+2],buf_ptr[total_size+3], + total_size+4); + + total_size += 4; + + } else if (pi->rds_event & RDS_EVENT_AF_MASK) { + pi->rds_event &= ~RDS_EVENT_AF_MASK; + buf_ptr[total_size] = RDS_EVENT_AF; + buf_ptr[total_size+1] = 2; + buf_ptr[total_size+2] = (u8)(pi->af_buf[0] & 0xFF); + buf_ptr[total_size+3] = (u8)(pi->af_buf[0] >> 8); + RDSEBUG(radio, + "[RDS] Enqueue AF data[%02X:%02X:%02X:%02X]. total=%d\n", + buf_ptr[total_size],buf_ptr[total_size+1],buf_ptr[total_size+2],buf_ptr[total_size+3], + total_size+4); + + total_size += 4; + + } else if (pi->rds_event & RDS_EVENT_PS_MASK) { + pi->rds_event &= ~RDS_EVENT_PS_MASK; + str_len = strlen(pi->ps_candidate); + buf_ptr[total_size] = RDS_EVENT_PS; + buf_ptr[total_size+1] = str_len; + + for (i = 0; i < str_len; i++) { + buf_ptr[total_size + i + 2] = pi->ps_candidate[i]; + } + + total_size += str_len + 2; + RDSEBUG(radio, + "[RDS] Enqueue PS data. total=%d,%d,%d\n", total_size, pi->ps_len + 2, str_len); + } else if (pi->rds_event & RDS_EVENT_RT_MASK) { + pi->rds_event &= ~RDS_EVENT_RT_MASK; + str_len = strlen(pi->rt_candidate); + buf_ptr[total_size] = RDS_EVENT_RT; + buf_ptr[total_size+1] = str_len; + + for (i = 0; i < str_len; i++) { + buf_ptr[total_size + i + 2] = pi->rt_candidate[i]; + } + + total_size += str_len + 2; + RDSEBUG(radio, + "[RDS] Enqueue RT data. total=%d,%d,%d\n", total_size, pi->rt_len + 2, str_len + 2); + } else if (pi->rds_event & RDS_EVENT_ECC_MASK) { + pi->rds_event &= ~RDS_EVENT_ECC_MASK; + + buf_ptr[total_size] = RDS_EVENT_ECC; + buf_ptr[total_size+1] = 1; + buf_ptr[total_size+2] = (u8)pi->ecc_buf[0]; + RDSEBUG(radio, + "[RDS] Enqueue ECC data[%02d:%02d:%02d]. total=%d\n", + buf_ptr[total_size],buf_ptr[total_size+1],buf_ptr[total_size+2], + total_size+3); + + total_size += 3; + } else if (pi->rds_event & RDS_EVENT_RTP_MASK) { + pi->rds_event &= ~RDS_EVENT_RTP_MASK; + + buf_ptr[total_size] = RDS_EVENT_RTP; + buf_ptr[total_size+1] = sizeof(struct rtp_info); + memcpy(buf_ptr+total_size+2, &(pi->rtp_data), sizeof(struct rtp_info)); + RDSEBUG(radio, "[RDS] Enqueue RTPlus data\n"); + RDSEBUG(radio, "[RDS] Toggle:%d, Running:%d, Validated;%d\n", pi->rtp_data.toggle, pi->rtp_data.running, pi->rtp_data.validated); + RDSEBUG(radio, "[%d,%d,%d]\n", pi->rtp_data.tag[0].content_type, pi->rtp_data.tag[0].start_pos, pi->rtp_data.tag[0].len); + RDSEBUG(radio, "[%d,%d,%d]\n", pi->rtp_data.tag[1].content_type, pi->rtp_data.tag[1].start_pos, pi->rtp_data.tag[1].len); + + total_size += sizeof(struct rtp_info) + 2; + } + + if (!pi->rds_event && total_size) { +#ifdef USE_RINGBUFF_API + if (!ringbuf_memcpy_into(&radio->rds_rb, buf_ptr, total_size)) { + usage = ringbuf_bytes_used(&radio->rds_rb); + dev_info(radio->dev, + "%s():>>>RB memcpy into fail! usage:%04d", + __func__, ringbuf_bytes_used(&radio->rds_rb)); + if (!usage) + return; + } + + usage = ringbuf_bytes_used(&radio->rds_rb); +#else + dev_info(radio->dev, ">>> ringbuff API not used, fail.."); +#endif /* USE_RINGBUFF_API */ + + if (atomic_read(&radio->is_rds_new)) + return; + + fm_set_flag_bits(radio, FLAG_BUF_FUL); + atomic_set(&radio->is_rds_new, 1); + wake_up_interruptible(&radio->core->rds_read_queue); + radio->rds_n_count++; + + if (!(radio->rds_n_count%200)) { + fm_update_rssi_work(radio); + dev_info(radio->dev, + ">>>[FM] RSSI[%03d] NCOUNT[%08d] FIFO_ERR[%08d] USAGE[%04d] SYNCLOSS[%08d]", + radio->low->fm_state.rssi, radio->rds_n_count, radio->rds_fifo_err_cnt, + usage, radio->rds_sync_loss_cnt); + } + } + } +} + +#ifndef USE_RDS_HW_DECODER +struct fm_decoded_data { + u16 info; + fm_rds_block_type_enum blk_type; + u8 err_cnt; +}; + +void put_bit_to_byte(u8 *fifo, u32 data) +{ + s8 i, j; + u32 mask = 0x80000000; + + for (i = BUF_LEN - 1, j = 0; i >= 0; i--, j++) { + *(fifo + j) = (mask & data) ? 1 : 0; + mask = mask >> 1; + } +} + +u32 get_block_data(u8 *fifo, u32 sp) +{ + u8 i, j; + u32 data = 0; + + data |= *(fifo + (sp++ % 160)); + + for (i = BLK_LEN-1, j = 0; i > 0; i--, j++) { + data <<= 1; + data |= *(fifo + (sp++ % 160)); + } + + return data; +} + +u8 find_code_word(u32 *data, fm_rds_block_type_enum b_type, bool seq_lock) +{ + u16 first13; + u16 second13; + u16 syndrome; + + first13 = *data >> 13; + second13 = (*data & 0x1FFF) ^ OFFSET_WORD[b_type]; + + syndrome = CRC_LUT[(CRC_LUT[first13] << 3) ^ second13]; + + if (syndrome) { + u32 corrected; + u8 i, j; + u8 maxerr = (b_type == RDS_BLKTYPE_A) ? 2 : sizeof(burstErrorPattern) / sizeof(u8); + + for (i = 0; i < maxerr; i++) { + for (j = 0; j <= BLK_LEN - burstErrorLen[i]; j++) { + corrected = *data ^ (burstErrorPattern[i] << j); + + first13 = corrected >> 13; + second13 = (corrected & 0x1FFF) ^ OFFSET_WORD[b_type]; + + syndrome = CRC_LUT[(CRC_LUT[first13] << 3) ^ second13]; + + if (!syndrome) { + *data = corrected; + return burstErrorLen[i]; + } + } + } + } else { + return 0; + } + return 6; +} + +void fm_process_rds_data(struct s610_radio *radio) +{ + u16 i, j, k, l; + u16 avail_blocks; + u32 fifo_data; + u32 data; + fm_host_rds_errors_enum err_cnt; + u8 min_pos = 0; + u8 min_blk = 0; + u8 min_blk_tmp = 0; + u8 min_pos_sum, min_blk_sum; + + static u32 idx; + static u8 fifo[160]; + static u32 start_pos; + static u32 end_pos; + static bool fetch_data; + static bool seq_lock; + static bool remains; + static struct fm_decoded_data decoded[BLK_LEN][RDS_NUM_BLOCK_TYPES - 1][RDS_NUM_BLOCK_TYPES - 1]; + u32 fifo_status; + + fifo_status = fmspeedy_get_reg_work(0xFFF398); + avail_blocks = (((fifo_status >> 8) & 0x3F)); + RDSEBUG(radio,"%s(): avail_blocks:%d fifo_status[%08X]",__func__, avail_blocks, fifo_status); + + while (avail_blocks) { + /* Fetch the RDS raw data. */ + if (fetch_data) { + fifo_data = fmspeedy_get_reg_work(0xFFF3C0); + put_bit_to_byte(fifo + 32 * ((idx++) % 5), fifo_data); + avail_blocks--; + } + + switch (fm_rds_get_state(radio)) { + case RDS_STATE_INIT: + fm_rds_change_state(radio, RDS_STATE_HAVE_DATA); + fm_update_rds_sync_status(radio, false); + radio->low->fm_rds_state.error_bits = 0; + radio->low->fm_rds_state.error_blks = 0; + + idx = 0; + start_pos = 0; + end_pos = BLK_LEN - 1; + fetch_data = false; + seq_lock = false; + memset(fifo, 0, sizeof(fifo) / sizeof(u8)); + + case RDS_STATE_HAVE_DATA: + if (idx < 5) { + fifo_data = fmspeedy_get_reg_work(0xFFF3C0); + put_bit_to_byte(fifo + 32 * ((idx++) % 5), fifo_data); + avail_blocks--; + + if (idx == 5) { + for (i = 0; i < BLK_LEN; i++) { + for (j = 0; j < RDS_NUM_BLOCK_TYPES - 1; j++) { + start_pos = i; + + for (k = 0, l = j; k < RDS_NUM_BLOCK_TYPES - 1; k++) { + data = get_block_data(fifo, start_pos); + err_cnt = find_code_word(&data, l, seq_lock); + + decoded[i][j][k].info = data >> 10; + decoded[i][j][k].blk_type = l; + decoded[i][j][k].err_cnt = err_cnt; + start_pos += BLK_LEN; + l = (l + 1) % (RDS_NUM_BLOCK_TYPES - 1); + } + } + } + + for (i = 0, min_pos_sum = 0xFF; i < BLK_LEN; i++) { + for (j = 0, min_blk_sum = 0xFF; j < RDS_NUM_BLOCK_TYPES - 1; j++) { + for (k = 0, err_cnt = 0; k < RDS_NUM_BLOCK_TYPES - 1; k++) { + err_cnt += decoded[i][j][k].err_cnt; + } + + if (min_blk_sum > err_cnt) { + min_blk_sum = err_cnt; + min_blk_tmp = j; + } + } + + if (min_pos_sum > min_blk_sum) { + min_pos_sum = min_blk_sum; + min_pos = i; + min_blk = min_blk_tmp; + } + + } + + fm_rds_change_state(radio, RDS_STATE_PRE_SYNC); + } else + break; + } + + case RDS_STATE_PRE_SYNC: + for (i = 0; i < RDS_NUM_BLOCK_TYPES - 1; i++) { + if (decoded[min_pos][min_blk][i].blk_type == RDS_BLKTYPE_A) { + fm_update_rds_sync_status(radio, TRUE); + } + + if (fm_get_rds_sync_status(radio)) { + + if (fm_rds_process_block(radio, decoded[min_pos][min_blk][i].info, + decoded[min_pos][min_blk][i].blk_type, decoded[min_pos][min_blk][i].err_cnt) + == HOST_RDS_ERRS_UNCORR) { + fm_rds_update_error_status(radio, + radio->low->fm_state.rds_unsync_uncorr_weight); + } else { + fm_rds_update_error_status(radio, decoded[min_pos][min_blk][i].err_cnt); + } + } + } + + start_pos = min_pos + BLK_LEN * 3; + end_pos = start_pos + BLK_LEN - 1; + + fm_rds_change_state(radio, (min_blk + 3) % (RDS_NUM_BLOCK_TYPES - 1)); + seq_lock = false; + remains = true; + + case RDS_STATE_FOUND_BL_A: + case RDS_STATE_FOUND_BL_B: + case RDS_STATE_FOUND_BL_C: + case RDS_STATE_FOUND_BL_D: + if ((end_pos / BUF_LEN != (end_pos + BLK_LEN) / BUF_LEN) && !fetch_data && !remains) { + fetch_data = true; + } else { + if (end_pos + BLK_LEN >= 160 && remains) { + remains = false; + fetch_data = true; + break; + } + + start_pos += BLK_LEN; + end_pos += BLK_LEN; + + data = get_block_data(fifo, start_pos); + fetch_data = false; + + i = (fm_rds_get_state(radio) + 1) % (RDS_NUM_BLOCK_TYPES - 1); + fm_rds_change_state(radio, i); + err_cnt = find_code_word(&data, i, seq_lock); + + RDSEBUG(radio,"%s(): data:0x%x, errcnt:%d, gcount:%d", __func__, data, err_cnt, radio->rds_gcnt++); + if (fm_rds_process_block(radio, data >> 10, i, err_cnt) == HOST_RDS_ERRS_UNCORR) { + fm_rds_update_error_status(radio, radio->low->fm_state.rds_unsync_uncorr_weight); + } else { + fm_rds_update_error_status(radio, err_cnt); + } + } + break; + + default: + break; + } + } +} +#endif /*USE_RDS_HW_DECODER*/ diff --git a/drivers/media/radio/s610/fm_rds.h b/drivers/media/radio/s610/fm_rds.h new file mode 100644 index 000000000000..c5fa66cac6e1 --- /dev/null +++ b/drivers/media/radio/s610/fm_rds.h @@ -0,0 +1,2148 @@ +#ifndef FM_RDS_H +#define FM_RDS_H + +void fm_rds_write_data(struct s610_radio *radio, u16 rds_data, + fm_rds_block_type_enum blk_type, fm_host_rds_errors_enum errors); +void fm_rds_write_data_remove(struct s610_radio *radio, + fm_rds_rm_align_enum removeblock); +u16 fm_rds_get_avail_bytes(struct s610_radio *radio); +int fm_read_rds_data(struct s610_radio *radio, u8 *buffer, int size, + u16 *blocks); +void fm_rds_change_state(struct s610_radio *radio, + fm_rds_state_enum new_state); +void fm_rds_update_error_status(struct s610_radio *radio, u16 errors); +static fm_host_rds_errors_enum fm_rds_process_block(struct s610_radio *radio, + u16 data, fm_rds_block_type_enum block_type, u16 err_count); +void fm_process_rds_data(struct s610_radio *radio); +int ringbuf_bytes_used(const struct ringbuf_t *rb); +void fm_rds_parser(struct fm_rds_parser_info *pi, u16 info, u8 blk_type, u8 err_cnt); +void fm_rds_write_data_pi(struct s610_radio *radio, + struct fm_rds_parser_info *pi); +void fm_rds_parser_reset(struct fm_rds_parser_info *pi); + +extern void fm_update_rds_sync_status(struct s610_radio *radio, bool synced); +#ifndef USE_RDS_HW_DECODER +extern bool fm_get_rds_sync_status(struct s610_radio *radio); +#endif /*USE_RDS_HW_DECODER*/ + +extern int fm_set_flags(struct s610_radio *radio, u16 flags); +extern void fm_timer_reset(fm_timer_t *timer, int usec, fm_callback_t *func, + void *arg); +extern u32 fmspeedy_get_reg(u32 addr); +extern u32 fmspeedy_get_reg_work(u32 addr); +extern void fm_update_rssi_work(struct s610_radio *radio); + +#define RDS_BLK_TYPE_MASK 0xE0 +#define RDS_BLK_TYPE_SHIFT 5 +#define RDS_ERR_CNT_MASK 0x1F +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#define USE_RDS_BLOCK_SEQ_CORRECT +/*#undef USE_RDS_BLOCK_SEQ_CORRECT*/ + +#define BUF_LEN 32 +#define BLK_LEN 26 + +#define RDS_EVENT_NONE_MASK 0x0 +#define RDS_EVENT_PI_MASK 0x1 +#define RDS_EVENT_PS_MASK 0x2 +#define RDS_EVENT_AF_MASK 0x4 +#define RDS_EVENT_RT_MASK 0x8 +#define RDS_EVENT_ECC_MASK 0x10 +#define RDS_EVENT_RTP_MASK 0x20 + +#define RDS_RTP_AID 0x4BD7 + +typedef enum { + RDS_EVENT_NONE = 0, + RDS_EVENT_PI, + RDS_EVENT_PS, + RDS_EVENT_AF, + RDS_EVENT_RT, + RDS_EVENT_ECC, + RDS_EVENT_RTP, +} fm_rds_event_type_enum; + +enum { + RDS_GRPTYPE_0A = 0x0, + RDS_GRPTYPE_0B = 0x1, + RDS_GRPTYPE_1A = 0x2, + RDS_GRPTYPE_2A = 0x4, + RDS_GRPTYPE_2B = 0x5, + RDS_GRPTYPE_3A = 0x6, + RDS_GRPTYPE_5A = 0xA, + RDS_GRPTYPE_6A = 0xC, + RDS_GRPTYPE_7A = 0xE, + RDS_GRPTYPE_8A = 0x10, + RDS_GRPTYPE_9A = 0x12, + RDS_GRPTYPE_11A = 0x16, + RDS_GRPTYPE_12A = 0x18, + RDS_GRPTYPE_13A = 0x1A, + RDS_GRPTYPE_NONE = 0xFF, +}; + +u16 OFFSET_WORD[] = { + 0xFC, + 0x198, + 0x168, + //0x350, /* Need to override C with C' if current version is B */ + 0x1B4, +}; + +u8 burstErrorPattern[] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 }; +u8 burstErrorLen[] = { 1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5 }; + +#ifndef USE_RDS_HW_DECODER +u16 CRC_LUT[] = { +0x0000, 0x01b9, 0x0372, 0x02cb, +0x035d, 0x02e4, 0x002f, 0x0196, +0x0303, 0x02ba, 0x0071, 0x01c8, +0x005e, 0x01e7, 0x032c, 0x0295, +0x03bf, 0x0206, 0x00cd, 0x0174, +0x00e2, 0x015b, 0x0390, 0x0229, +0x00bc, 0x0105, 0x03ce, 0x0277, +0x03e1, 0x0258, 0x0093, 0x012a, +0x02c7, 0x037e, 0x01b5, 0x000c, +0x019a, 0x0023, 0x02e8, 0x0351, +0x01c4, 0x007d, 0x02b6, 0x030f, +0x0299, 0x0320, 0x01eb, 0x0052, +0x0178, 0x00c1, 0x020a, 0x03b3, +0x0225, 0x039c, 0x0157, 0x00ee, +0x027b, 0x03c2, 0x0109, 0x00b0, +0x0126, 0x009f, 0x0254, 0x03ed, +0x0037, 0x018e, 0x0345, 0x02fc, +0x036a, 0x02d3, 0x0018, 0x01a1, +0x0334, 0x028d, 0x0046, 0x01ff, +0x0069, 0x01d0, 0x031b, 0x02a2, +0x0388, 0x0231, 0x00fa, 0x0143, +0x00d5, 0x016c, 0x03a7, 0x021e, +0x008b, 0x0132, 0x03f9, 0x0240, +0x03d6, 0x026f, 0x00a4, 0x011d, +0x02f0, 0x0349, 0x0182, 0x003b, +0x01ad, 0x0014, 0x02df, 0x0366, +0x01f3, 0x004a, 0x0281, 0x0338, +0x02ae, 0x0317, 0x01dc, 0x0065, +0x014f, 0x00f6, 0x023d, 0x0384, +0x0212, 0x03ab, 0x0160, 0x00d9, +0x024c, 0x03f5, 0x013e, 0x0087, +0x0111, 0x00a8, 0x0263, 0x03da, +0x006e, 0x01d7, 0x031c, 0x02a5, +0x0333, 0x028a, 0x0041, 0x01f8, +0x036d, 0x02d4, 0x001f, 0x01a6, +0x0030, 0x0189, 0x0342, 0x02fb, +0x03d1, 0x0268, 0x00a3, 0x011a, +0x008c, 0x0135, 0x03fe, 0x0247, +0x00d2, 0x016b, 0x03a0, 0x0219, +0x038f, 0x0236, 0x00fd, 0x0144, +0x02a9, 0x0310, 0x01db, 0x0062, +0x01f4, 0x004d, 0x0286, 0x033f, +0x01aa, 0x0013, 0x02d8, 0x0361, +0x02f7, 0x034e, 0x0185, 0x003c, +0x0116, 0x00af, 0x0264, 0x03dd, +0x024b, 0x03f2, 0x0139, 0x0080, +0x0215, 0x03ac, 0x0167, 0x00de, +0x0148, 0x00f1, 0x023a, 0x0383, +0x0059, 0x01e0, 0x032b, 0x0292, +0x0304, 0x02bd, 0x0076, 0x01cf, +0x035a, 0x02e3, 0x0028, 0x0191, +0x0007, 0x01be, 0x0375, 0x02cc, +0x03e6, 0x025f, 0x0094, 0x012d, +0x00bb, 0x0102, 0x03c9, 0x0270, +0x00e5, 0x015c, 0x0397, 0x022e, +0x03b8, 0x0201, 0x00ca, 0x0173, +0x029e, 0x0327, 0x01ec, 0x0055, +0x01c3, 0x007a, 0x02b1, 0x0308, +0x019d, 0x0024, 0x02ef, 0x0356, +0x02c0, 0x0379, 0x01b2, 0x000b, +0x0121, 0x0098, 0x0253, 0x03ea, +0x027c, 0x03c5, 0x010e, 0x00b7, +0x0222, 0x039b, 0x0150, 0x00e9, +0x017f, 0x00c6, 0x020d, 0x03b4, +0x00dc, 0x0165, 0x03ae, 0x0217, +0x0381, 0x0238, 0x00f3, 0x014a, +0x03df, 0x0266, 0x00ad, 0x0114, +0x0082, 0x013b, 0x03f0, 0x0249, +0x0363, 0x02da, 0x0011, 0x01a8, +0x003e, 0x0187, 0x034c, 0x02f5, +0x0060, 0x01d9, 0x0312, 0x02ab, +0x033d, 0x0284, 0x004f, 0x01f6, +0x021b, 0x03a2, 0x0169, 0x00d0, +0x0146, 0x00ff, 0x0234, 0x038d, +0x0118, 0x00a1, 0x026a, 0x03d3, +0x0245, 0x03fc, 0x0137, 0x008e, +0x01a4, 0x001d, 0x02d6, 0x036f, +0x02f9, 0x0340, 0x018b, 0x0032, +0x02a7, 0x031e, 0x01d5, 0x006c, +0x01fa, 0x0043, 0x0288, 0x0331, +0x00eb, 0x0152, 0x0399, 0x0220, +0x03b6, 0x020f, 0x00c4, 0x017d, +0x03e8, 0x0251, 0x009a, 0x0123, +0x00b5, 0x010c, 0x03c7, 0x027e, +0x0354, 0x02ed, 0x0026, 0x019f, +0x0009, 0x01b0, 0x037b, 0x02c2, +0x0057, 0x01ee, 0x0325, 0x029c, +0x030a, 0x02b3, 0x0078, 0x01c1, +0x022c, 0x0395, 0x015e, 0x00e7, +0x0171, 0x00c8, 0x0203, 0x03ba, +0x012f, 0x0096, 0x025d, 0x03e4, +0x0272, 0x03cb, 0x0100, 0x00b9, +0x0193, 0x002a, 0x02e1, 0x0358, +0x02ce, 0x0377, 0x01bc, 0x0005, +0x0290, 0x0329, 0x01e2, 0x005b, +0x01cd, 0x0074, 0x02bf, 0x0306, +0x00b2, 0x010b, 0x03c0, 0x0279, +0x03ef, 0x0256, 0x009d, 0x0124, +0x03b1, 0x0208, 0x00c3, 0x017a, +0x00ec, 0x0155, 0x039e, 0x0227, +0x030d, 0x02b4, 0x007f, 0x01c6, +0x0050, 0x01e9, 0x0322, 0x029b, +0x000e, 0x01b7, 0x037c, 0x02c5, +0x0353, 0x02ea, 0x0021, 0x0198, +0x0275, 0x03cc, 0x0107, 0x00be, +0x0128, 0x0091, 0x025a, 0x03e3, +0x0176, 0x00cf, 0x0204, 0x03bd, +0x022b, 0x0392, 0x0159, 0x00e0, +0x01ca, 0x0073, 0x02b8, 0x0301, +0x0297, 0x032e, 0x01e5, 0x005c, +0x02c9, 0x0370, 0x01bb, 0x0002, +0x0194, 0x002d, 0x02e6, 0x035f, +0x0085, 0x013c, 0x03f7, 0x024e, +0x03d8, 0x0261, 0x00aa, 0x0113, +0x0386, 0x023f, 0x00f4, 0x014d, +0x00db, 0x0162, 0x03a9, 0x0210, +0x033a, 0x0283, 0x0048, 0x01f1, +0x0067, 0x01de, 0x0315, 0x02ac, +0x0039, 0x0180, 0x034b, 0x02f2, +0x0364, 0x02dd, 0x0016, 0x01af, +0x0242, 0x03fb, 0x0130, 0x0089, +0x011f, 0x00a6, 0x026d, 0x03d4, +0x0141, 0x00f8, 0x0233, 0x038a, +0x021c, 0x03a5, 0x016e, 0x00d7, +0x01fd, 0x0044, 0x028f, 0x0336, +0x02a0, 0x0319, 0x01d2, 0x006b, +0x02fe, 0x0347, 0x018c, 0x0035, +0x01a3, 0x001a, 0x02d1, 0x0368, +0x01b8, 0x0001, 0x02ca, 0x0373, +0x02e5, 0x035c, 0x0197, 0x002e, +0x02bb, 0x0302, 0x01c9, 0x0070, +0x01e6, 0x005f, 0x0294, 0x032d, +0x0207, 0x03be, 0x0175, 0x00cc, +0x015a, 0x00e3, 0x0228, 0x0391, +0x0104, 0x00bd, 0x0276, 0x03cf, +0x0259, 0x03e0, 0x012b, 0x0092, +0x037f, 0x02c6, 0x000d, 0x01b4, +0x0022, 0x019b, 0x0350, 0x02e9, +0x007c, 0x01c5, 0x030e, 0x02b7, +0x0321, 0x0298, 0x0053, 0x01ea, +0x00c0, 0x0179, 0x03b2, 0x020b, +0x039d, 0x0224, 0x00ef, 0x0156, +0x03c3, 0x027a, 0x00b1, 0x0108, +0x009e, 0x0127, 0x03ec, 0x0255, +0x018f, 0x0036, 0x02fd, 0x0344, +0x02d2, 0x036b, 0x01a0, 0x0019, +0x028c, 0x0335, 0x01fe, 0x0047, +0x01d1, 0x0068, 0x02a3, 0x031a, +0x0230, 0x0389, 0x0142, 0x00fb, +0x016d, 0x00d4, 0x021f, 0x03a6, +0x0133, 0x008a, 0x0241, 0x03f8, +0x026e, 0x03d7, 0x011c, 0x00a5, +0x0348, 0x02f1, 0x003a, 0x0183, +0x0015, 0x01ac, 0x0367, 0x02de, +0x004b, 0x01f2, 0x0339, 0x0280, +0x0316, 0x02af, 0x0064, 0x01dd, +0x00f7, 0x014e, 0x0385, 0x023c, +0x03aa, 0x0213, 0x00d8, 0x0161, +0x03f4, 0x024d, 0x0086, 0x013f, +0x00a9, 0x0110, 0x03db, 0x0262, +0x01d6, 0x006f, 0x02a4, 0x031d, +0x028b, 0x0332, 0x01f9, 0x0040, +0x02d5, 0x036c, 0x01a7, 0x001e, +0x0188, 0x0031, 0x02fa, 0x0343, +0x0269, 0x03d0, 0x011b, 0x00a2, +0x0134, 0x008d, 0x0246, 0x03ff, +0x016a, 0x00d3, 0x0218, 0x03a1, +0x0237, 0x038e, 0x0145, 0x00fc, +0x0311, 0x02a8, 0x0063, 0x01da, +0x004c, 0x01f5, 0x033e, 0x0287, +0x0012, 0x01ab, 0x0360, 0x02d9, +0x034f, 0x02f6, 0x003d, 0x0184, +0x00ae, 0x0117, 0x03dc, 0x0265, +0x03f3, 0x024a, 0x0081, 0x0138, +0x03ad, 0x0214, 0x00df, 0x0166, +0x00f0, 0x0149, 0x0382, 0x023b, +0x01e1, 0x0058, 0x0293, 0x032a, +0x02bc, 0x0305, 0x01ce, 0x0077, +0x02e2, 0x035b, 0x0190, 0x0029, +0x01bf, 0x0006, 0x02cd, 0x0374, +0x025e, 0x03e7, 0x012c, 0x0095, +0x0103, 0x00ba, 0x0271, 0x03c8, +0x015d, 0x00e4, 0x022f, 0x0396, +0x0200, 0x03b9, 0x0172, 0x00cb, +0x0326, 0x029f, 0x0054, 0x01ed, +0x007b, 0x01c2, 0x0309, 0x02b0, +0x0025, 0x019c, 0x0357, 0x02ee, +0x0378, 0x02c1, 0x000a, 0x01b3, +0x0099, 0x0120, 0x03eb, 0x0252, +0x03c4, 0x027d, 0x00b6, 0x010f, +0x039a, 0x0223, 0x00e8, 0x0151, +0x00c7, 0x017e, 0x03b5, 0x020c, +0x0164, 0x00dd, 0x0216, 0x03af, +0x0239, 0x0380, 0x014b, 0x00f2, +0x0267, 0x03de, 0x0115, 0x00ac, +0x013a, 0x0083, 0x0248, 0x03f1, +0x02db, 0x0362, 0x01a9, 0x0010, +0x0186, 0x003f, 0x02f4, 0x034d, +0x01d8, 0x0061, 0x02aa, 0x0313, +0x0285, 0x033c, 0x01f7, 0x004e, +0x03a3, 0x021a, 0x00d1, 0x0168, +0x00fe, 0x0147, 0x038c, 0x0235, +0x00a0, 0x0119, 0x03d2, 0x026b, +0x03fd, 0x0244, 0x008f, 0x0136, +0x001c, 0x01a5, 0x036e, 0x02d7, +0x0341, 0x02f8, 0x0033, 0x018a, +0x031f, 0x02a6, 0x006d, 0x01d4, +0x0042, 0x01fb, 0x0330, 0x0289, +0x0153, 0x00ea, 0x0221, 0x0398, +0x020e, 0x03b7, 0x017c, 0x00c5, +0x0250, 0x03e9, 0x0122, 0x009b, +0x010d, 0x00b4, 0x027f, 0x03c6, +0x02ec, 0x0355, 0x019e, 0x0027, +0x01b1, 0x0008, 0x02c3, 0x037a, +0x01ef, 0x0056, 0x029d, 0x0324, +0x02b2, 0x030b, 0x01c0, 0x0079, +0x0394, 0x022d, 0x00e6, 0x015f, +0x00c9, 0x0170, 0x03bb, 0x0202, +0x0097, 0x012e, 0x03e5, 0x025c, +0x03ca, 0x0273, 0x00b8, 0x0101, +0x002b, 0x0192, 0x0359, 0x02e0, +0x0376, 0x02cf, 0x0004, 0x01bd, +0x0328, 0x0291, 0x005a, 0x01e3, +0x0075, 0x01cc, 0x0307, 0x02be, +0x010a, 0x00b3, 0x0278, 0x03c1, +0x0257, 0x03ee, 0x0125, 0x009c, +0x0209, 0x03b0, 0x017b, 0x00c2, +0x0154, 0x00ed, 0x0226, 0x039f, +0x02b5, 0x030c, 0x01c7, 0x007e, +0x01e8, 0x0051, 0x029a, 0x0323, +0x01b6, 0x000f, 0x02c4, 0x037d, +0x02eb, 0x0352, 0x0199, 0x0020, +0x03cd, 0x0274, 0x00bf, 0x0106, +0x0090, 0x0129, 0x03e2, 0x025b, +0x00ce, 0x0177, 0x03bc, 0x0205, +0x0393, 0x022a, 0x00e1, 0x0158, +0x0072, 0x01cb, 0x0300, 0x02b9, +0x032f, 0x0296, 0x005d, 0x01e4, +0x0371, 0x02c8, 0x0003, 0x01ba, +0x002c, 0x0195, 0x035e, 0x02e7, +0x013d, 0x0084, 0x024f, 0x03f6, +0x0260, 0x03d9, 0x0112, 0x00ab, +0x023e, 0x0387, 0x014c, 0x00f5, +0x0163, 0x00da, 0x0211, 0x03a8, +0x0282, 0x033b, 0x01f0, 0x0049, +0x01df, 0x0066, 0x02ad, 0x0314, +0x0181, 0x0038, 0x02f3, 0x034a, +0x02dc, 0x0365, 0x01ae, 0x0017, +0x03fa, 0x0243, 0x0088, 0x0131, +0x00a7, 0x011e, 0x03d5, 0x026c, +0x00f9, 0x0140, 0x038b, 0x0232, +0x03a4, 0x021d, 0x00d6, 0x016f, +0x0045, 0x01fc, 0x0337, 0x028e, +0x0318, 0x02a1, 0x006a, 0x01d3, +0x0346, 0x02ff, 0x0034, 0x018d, +0x001b, 0x01a2, 0x0369, 0x02d0, +0x0370, 0x02c9, 0x0002, 0x01bb, +0x002d, 0x0194, 0x035f, 0x02e6, +0x0073, 0x01ca, 0x0301, 0x02b8, +0x032e, 0x0297, 0x005c, 0x01e5, +0x00cf, 0x0176, 0x03bd, 0x0204, +0x0392, 0x022b, 0x00e0, 0x0159, +0x03cc, 0x0275, 0x00be, 0x0107, +0x0091, 0x0128, 0x03e3, 0x025a, +0x01b7, 0x000e, 0x02c5, 0x037c, +0x02ea, 0x0353, 0x0198, 0x0021, +0x02b4, 0x030d, 0x01c6, 0x007f, +0x01e9, 0x0050, 0x029b, 0x0322, +0x0208, 0x03b1, 0x017a, 0x00c3, +0x0155, 0x00ec, 0x0227, 0x039e, +0x010b, 0x00b2, 0x0279, 0x03c0, +0x0256, 0x03ef, 0x0124, 0x009d, +0x0347, 0x02fe, 0x0035, 0x018c, +0x001a, 0x01a3, 0x0368, 0x02d1, +0x0044, 0x01fd, 0x0336, 0x028f, +0x0319, 0x02a0, 0x006b, 0x01d2, +0x00f8, 0x0141, 0x038a, 0x0233, +0x03a5, 0x021c, 0x00d7, 0x016e, +0x03fb, 0x0242, 0x0089, 0x0130, +0x00a6, 0x011f, 0x03d4, 0x026d, +0x0180, 0x0039, 0x02f2, 0x034b, +0x02dd, 0x0364, 0x01af, 0x0016, +0x0283, 0x033a, 0x01f1, 0x0048, +0x01de, 0x0067, 0x02ac, 0x0315, +0x023f, 0x0386, 0x014d, 0x00f4, +0x0162, 0x00db, 0x0210, 0x03a9, +0x013c, 0x0085, 0x024e, 0x03f7, +0x0261, 0x03d8, 0x0113, 0x00aa, +0x031e, 0x02a7, 0x006c, 0x01d5, +0x0043, 0x01fa, 0x0331, 0x0288, +0x001d, 0x01a4, 0x036f, 0x02d6, +0x0340, 0x02f9, 0x0032, 0x018b, +0x00a1, 0x0118, 0x03d3, 0x026a, +0x03fc, 0x0245, 0x008e, 0x0137, +0x03a2, 0x021b, 0x00d0, 0x0169, +0x00ff, 0x0146, 0x038d, 0x0234, +0x01d9, 0x0060, 0x02ab, 0x0312, +0x0284, 0x033d, 0x01f6, 0x004f, +0x02da, 0x0363, 0x01a8, 0x0011, +0x0187, 0x003e, 0x02f5, 0x034c, +0x0266, 0x03df, 0x0114, 0x00ad, +0x013b, 0x0082, 0x0249, 0x03f0, +0x0165, 0x00dc, 0x0217, 0x03ae, +0x0238, 0x0381, 0x014a, 0x00f3, +0x0329, 0x0290, 0x005b, 0x01e2, +0x0074, 0x01cd, 0x0306, 0x02bf, +0x002a, 0x0193, 0x0358, 0x02e1, +0x0377, 0x02ce, 0x0005, 0x01bc, +0x0096, 0x012f, 0x03e4, 0x025d, +0x03cb, 0x0272, 0x00b9, 0x0100, +0x0395, 0x022c, 0x00e7, 0x015e, +0x00c8, 0x0171, 0x03ba, 0x0203, +0x01ee, 0x0057, 0x029c, 0x0325, +0x02b3, 0x030a, 0x01c1, 0x0078, +0x02ed, 0x0354, 0x019f, 0x0026, +0x01b0, 0x0009, 0x02c2, 0x037b, +0x0251, 0x03e8, 0x0123, 0x009a, +0x010c, 0x00b5, 0x027e, 0x03c7, +0x0152, 0x00eb, 0x0220, 0x0399, +0x020f, 0x03b6, 0x017d, 0x00c4, +0x03ac, 0x0215, 0x00de, 0x0167, +0x00f1, 0x0148, 0x0383, 0x023a, +0x00af, 0x0116, 0x03dd, 0x0264, +0x03f2, 0x024b, 0x0080, 0x0139, +0x0013, 0x01aa, 0x0361, 0x02d8, +0x034e, 0x02f7, 0x003c, 0x0185, +0x0310, 0x02a9, 0x0062, 0x01db, +0x004d, 0x01f4, 0x033f, 0x0286, +0x016b, 0x00d2, 0x0219, 0x03a0, +0x0236, 0x038f, 0x0144, 0x00fd, +0x0268, 0x03d1, 0x011a, 0x00a3, +0x0135, 0x008c, 0x0247, 0x03fe, +0x02d4, 0x036d, 0x01a6, 0x001f, +0x0189, 0x0030, 0x02fb, 0x0342, +0x01d7, 0x006e, 0x02a5, 0x031c, +0x028a, 0x0333, 0x01f8, 0x0041, +0x039b, 0x0222, 0x00e9, 0x0150, +0x00c6, 0x017f, 0x03b4, 0x020d, +0x0098, 0x0121, 0x03ea, 0x0253, +0x03c5, 0x027c, 0x00b7, 0x010e, +0x0024, 0x019d, 0x0356, 0x02ef, +0x0379, 0x02c0, 0x000b, 0x01b2, +0x0327, 0x029e, 0x0055, 0x01ec, +0x007a, 0x01c3, 0x0308, 0x02b1, +0x015c, 0x00e5, 0x022e, 0x0397, +0x0201, 0x03b8, 0x0173, 0x00ca, +0x025f, 0x03e6, 0x012d, 0x0094, +0x0102, 0x00bb, 0x0270, 0x03c9, +0x02e3, 0x035a, 0x0191, 0x0028, +0x01be, 0x0007, 0x02cc, 0x0375, +0x01e0, 0x0059, 0x0292, 0x032b, +0x02bd, 0x0304, 0x01cf, 0x0076, +0x03c2, 0x027b, 0x00b0, 0x0109, +0x009f, 0x0126, 0x03ed, 0x0254, +0x00c1, 0x0178, 0x03b3, 0x020a, +0x039c, 0x0225, 0x00ee, 0x0157, +0x007d, 0x01c4, 0x030f, 0x02b6, +0x0320, 0x0299, 0x0052, 0x01eb, +0x037e, 0x02c7, 0x000c, 0x01b5, +0x0023, 0x019a, 0x0351, 0x02e8, +0x0105, 0x00bc, 0x0277, 0x03ce, +0x0258, 0x03e1, 0x012a, 0x0093, +0x0206, 0x03bf, 0x0174, 0x00cd, +0x015b, 0x00e2, 0x0229, 0x0390, +0x02ba, 0x0303, 0x01c8, 0x0071, +0x01e7, 0x005e, 0x0295, 0x032c, +0x01b9, 0x0000, 0x02cb, 0x0372, +0x02e4, 0x035d, 0x0196, 0x002f, +0x03f5, 0x024c, 0x0087, 0x013e, +0x00a8, 0x0111, 0x03da, 0x0263, +0x00f6, 0x014f, 0x0384, 0x023d, +0x03ab, 0x0212, 0x00d9, 0x0160, +0x004a, 0x01f3, 0x0338, 0x0281, +0x0317, 0x02ae, 0x0065, 0x01dc, +0x0349, 0x02f0, 0x003b, 0x0182, +0x0014, 0x01ad, 0x0366, 0x02df, +0x0132, 0x008b, 0x0240, 0x03f9, +0x026f, 0x03d6, 0x011d, 0x00a4, +0x0231, 0x0388, 0x0143, 0x00fa, +0x016c, 0x00d5, 0x021e, 0x03a7, +0x028d, 0x0334, 0x01ff, 0x0046, +0x01d0, 0x0069, 0x02a2, 0x031b, +0x018e, 0x0037, 0x02fc, 0x0345, +0x02d3, 0x036a, 0x01a1, 0x0018, +0x02c8, 0x0371, 0x01ba, 0x0003, +0x0195, 0x002c, 0x02e7, 0x035e, +0x01cb, 0x0072, 0x02b9, 0x0300, +0x0296, 0x032f, 0x01e4, 0x005d, +0x0177, 0x00ce, 0x0205, 0x03bc, +0x022a, 0x0393, 0x0158, 0x00e1, +0x0274, 0x03cd, 0x0106, 0x00bf, +0x0129, 0x0090, 0x025b, 0x03e2, +0x000f, 0x01b6, 0x037d, 0x02c4, +0x0352, 0x02eb, 0x0020, 0x0199, +0x030c, 0x02b5, 0x007e, 0x01c7, +0x0051, 0x01e8, 0x0323, 0x029a, +0x03b0, 0x0209, 0x00c2, 0x017b, +0x00ed, 0x0154, 0x039f, 0x0226, +0x00b3, 0x010a, 0x03c1, 0x0278, +0x03ee, 0x0257, 0x009c, 0x0125, +0x02ff, 0x0346, 0x018d, 0x0034, +0x01a2, 0x001b, 0x02d0, 0x0369, +0x01fc, 0x0045, 0x028e, 0x0337, +0x02a1, 0x0318, 0x01d3, 0x006a, +0x0140, 0x00f9, 0x0232, 0x038b, +0x021d, 0x03a4, 0x016f, 0x00d6, +0x0243, 0x03fa, 0x0131, 0x0088, +0x011e, 0x00a7, 0x026c, 0x03d5, +0x0038, 0x0181, 0x034a, 0x02f3, +0x0365, 0x02dc, 0x0017, 0x01ae, +0x033b, 0x0282, 0x0049, 0x01f0, +0x0066, 0x01df, 0x0314, 0x02ad, +0x0387, 0x023e, 0x00f5, 0x014c, +0x00da, 0x0163, 0x03a8, 0x0211, +0x0084, 0x013d, 0x03f6, 0x024f, +0x03d9, 0x0260, 0x00ab, 0x0112, +0x02a6, 0x031f, 0x01d4, 0x006d, +0x01fb, 0x0042, 0x0289, 0x0330, +0x01a5, 0x001c, 0x02d7, 0x036e, +0x02f8, 0x0341, 0x018a, 0x0033, +0x0119, 0x00a0, 0x026b, 0x03d2, +0x0244, 0x03fd, 0x0136, 0x008f, +0x021a, 0x03a3, 0x0168, 0x00d1, +0x0147, 0x00fe, 0x0235, 0x038c, +0x0061, 0x01d8, 0x0313, 0x02aa, +0x033c, 0x0285, 0x004e, 0x01f7, +0x0362, 0x02db, 0x0010, 0x01a9, +0x003f, 0x0186, 0x034d, 0x02f4, +0x03de, 0x0267, 0x00ac, 0x0115, +0x0083, 0x013a, 0x03f1, 0x0248, +0x00dd, 0x0164, 0x03af, 0x0216, +0x0380, 0x0239, 0x00f2, 0x014b, +0x0291, 0x0328, 0x01e3, 0x005a, +0x01cc, 0x0075, 0x02be, 0x0307, +0x0192, 0x002b, 0x02e0, 0x0359, +0x02cf, 0x0376, 0x01bd, 0x0004, +0x012e, 0x0097, 0x025c, 0x03e5, +0x0273, 0x03ca, 0x0101, 0x00b8, +0x022d, 0x0394, 0x015f, 0x00e6, +0x0170, 0x00c9, 0x0202, 0x03bb, +0x0056, 0x01ef, 0x0324, 0x029d, +0x030b, 0x02b2, 0x0079, 0x01c0, +0x0355, 0x02ec, 0x0027, 0x019e, +0x0008, 0x01b1, 0x037a, 0x02c3, +0x03e9, 0x0250, 0x009b, 0x0122, +0x00b4, 0x010d, 0x03c6, 0x027f, +0x00ea, 0x0153, 0x0398, 0x0221, +0x03b7, 0x020e, 0x00c5, 0x017c, +0x0214, 0x03ad, 0x0166, 0x00df, +0x0149, 0x00f0, 0x023b, 0x0382, +0x0117, 0x00ae, 0x0265, 0x03dc, +0x024a, 0x03f3, 0x0138, 0x0081, +0x01ab, 0x0012, 0x02d9, 0x0360, +0x02f6, 0x034f, 0x0184, 0x003d, +0x02a8, 0x0311, 0x01da, 0x0063, +0x01f5, 0x004c, 0x0287, 0x033e, +0x00d3, 0x016a, 0x03a1, 0x0218, +0x038e, 0x0237, 0x00fc, 0x0145, +0x03d0, 0x0269, 0x00a2, 0x011b, +0x008d, 0x0134, 0x03ff, 0x0246, +0x036c, 0x02d5, 0x001e, 0x01a7, +0x0031, 0x0188, 0x0343, 0x02fa, +0x006f, 0x01d6, 0x031d, 0x02a4, +0x0332, 0x028b, 0x0040, 0x01f9, +0x0223, 0x039a, 0x0151, 0x00e8, +0x017e, 0x00c7, 0x020c, 0x03b5, +0x0120, 0x0099, 0x0252, 0x03eb, +0x027d, 0x03c4, 0x010f, 0x00b6, +0x019c, 0x0025, 0x02ee, 0x0357, +0x02c1, 0x0378, 0x01b3, 0x000a, +0x029f, 0x0326, 0x01ed, 0x0054, +0x01c2, 0x007b, 0x02b0, 0x0309, +0x00e4, 0x015d, 0x0396, 0x022f, +0x03b9, 0x0200, 0x00cb, 0x0172, +0x03e7, 0x025e, 0x0095, 0x012c, +0x00ba, 0x0103, 0x03c8, 0x0271, +0x035b, 0x02e2, 0x0029, 0x0190, +0x0006, 0x01bf, 0x0374, 0x02cd, +0x0058, 0x01e1, 0x032a, 0x0293, +0x0305, 0x02bc, 0x0077, 0x01ce, +0x027a, 0x03c3, 0x0108, 0x00b1, +0x0127, 0x009e, 0x0255, 0x03ec, +0x0179, 0x00c0, 0x020b, 0x03b2, +0x0224, 0x039d, 0x0156, 0x00ef, +0x01c5, 0x007c, 0x02b7, 0x030e, +0x0298, 0x0321, 0x01ea, 0x0053, +0x02c6, 0x037f, 0x01b4, 0x000d, +0x019b, 0x0022, 0x02e9, 0x0350, +0x00bd, 0x0104, 0x03cf, 0x0276, +0x03e0, 0x0259, 0x0092, 0x012b, +0x03be, 0x0207, 0x00cc, 0x0175, +0x00e3, 0x015a, 0x0391, 0x0228, +0x0302, 0x02bb, 0x0070, 0x01c9, +0x005f, 0x01e6, 0x032d, 0x0294, +0x0001, 0x01b8, 0x0373, 0x02ca, +0x035c, 0x02e5, 0x002e, 0x0197, +0x024d, 0x03f4, 0x013f, 0x0086, +0x0110, 0x00a9, 0x0262, 0x03db, +0x014e, 0x00f7, 0x023c, 0x0385, +0x0213, 0x03aa, 0x0161, 0x00d8, +0x01f2, 0x004b, 0x0280, 0x0339, +0x02af, 0x0316, 0x01dd, 0x0064, +0x02f1, 0x0348, 0x0183, 0x003a, +0x01ac, 0x0015, 0x02de, 0x0367, +0x008a, 0x0133, 0x03f8, 0x0241, +0x03d7, 0x026e, 0x00a5, 0x011c, +0x0389, 0x0230, 0x00fb, 0x0142, +0x00d4, 0x016d, 0x03a6, 0x021f, +0x0335, 0x028c, 0x0047, 0x01fe, +0x0068, 0x01d1, 0x031a, 0x02a3, +0x0036, 0x018f, 0x0344, 0x02fd, +0x036b, 0x02d2, 0x0019, 0x01a0, +0x0359, 0x02e0, 0x002b, 0x0192, +0x0004, 0x01bd, 0x0376, 0x02cf, +0x005a, 0x01e3, 0x0328, 0x0291, +0x0307, 0x02be, 0x0075, 0x01cc, +0x00e6, 0x015f, 0x0394, 0x022d, +0x03bb, 0x0202, 0x00c9, 0x0170, +0x03e5, 0x025c, 0x0097, 0x012e, +0x00b8, 0x0101, 0x03ca, 0x0273, +0x019e, 0x0027, 0x02ec, 0x0355, +0x02c3, 0x037a, 0x01b1, 0x0008, +0x029d, 0x0324, 0x01ef, 0x0056, +0x01c0, 0x0079, 0x02b2, 0x030b, +0x0221, 0x0398, 0x0153, 0x00ea, +0x017c, 0x00c5, 0x020e, 0x03b7, +0x0122, 0x009b, 0x0250, 0x03e9, +0x027f, 0x03c6, 0x010d, 0x00b4, +0x036e, 0x02d7, 0x001c, 0x01a5, +0x0033, 0x018a, 0x0341, 0x02f8, +0x006d, 0x01d4, 0x031f, 0x02a6, +0x0330, 0x0289, 0x0042, 0x01fb, +0x00d1, 0x0168, 0x03a3, 0x021a, +0x038c, 0x0235, 0x00fe, 0x0147, +0x03d2, 0x026b, 0x00a0, 0x0119, +0x008f, 0x0136, 0x03fd, 0x0244, +0x01a9, 0x0010, 0x02db, 0x0362, +0x02f4, 0x034d, 0x0186, 0x003f, +0x02aa, 0x0313, 0x01d8, 0x0061, +0x01f7, 0x004e, 0x0285, 0x033c, +0x0216, 0x03af, 0x0164, 0x00dd, +0x014b, 0x00f2, 0x0239, 0x0380, +0x0115, 0x00ac, 0x0267, 0x03de, +0x0248, 0x03f1, 0x013a, 0x0083, +0x0337, 0x028e, 0x0045, 0x01fc, +0x006a, 0x01d3, 0x0318, 0x02a1, +0x0034, 0x018d, 0x0346, 0x02ff, +0x0369, 0x02d0, 0x001b, 0x01a2, +0x0088, 0x0131, 0x03fa, 0x0243, +0x03d5, 0x026c, 0x00a7, 0x011e, +0x038b, 0x0232, 0x00f9, 0x0140, +0x00d6, 0x016f, 0x03a4, 0x021d, +0x01f0, 0x0049, 0x0282, 0x033b, +0x02ad, 0x0314, 0x01df, 0x0066, +0x02f3, 0x034a, 0x0181, 0x0038, +0x01ae, 0x0017, 0x02dc, 0x0365, +0x024f, 0x03f6, 0x013d, 0x0084, +0x0112, 0x00ab, 0x0260, 0x03d9, +0x014c, 0x00f5, 0x023e, 0x0387, +0x0211, 0x03a8, 0x0163, 0x00da, +0x0300, 0x02b9, 0x0072, 0x01cb, +0x005d, 0x01e4, 0x032f, 0x0296, +0x0003, 0x01ba, 0x0371, 0x02c8, +0x035e, 0x02e7, 0x002c, 0x0195, +0x00bf, 0x0106, 0x03cd, 0x0274, +0x03e2, 0x025b, 0x0090, 0x0129, +0x03bc, 0x0205, 0x00ce, 0x0177, +0x00e1, 0x0158, 0x0393, 0x022a, +0x01c7, 0x007e, 0x02b5, 0x030c, +0x029a, 0x0323, 0x01e8, 0x0051, +0x02c4, 0x037d, 0x01b6, 0x000f, +0x0199, 0x0020, 0x02eb, 0x0352, +0x0278, 0x03c1, 0x010a, 0x00b3, +0x0125, 0x009c, 0x0257, 0x03ee, +0x017b, 0x00c2, 0x0209, 0x03b0, +0x0226, 0x039f, 0x0154, 0x00ed, +0x0385, 0x023c, 0x00f7, 0x014e, +0x00d8, 0x0161, 0x03aa, 0x0213, +0x0086, 0x013f, 0x03f4, 0x024d, +0x03db, 0x0262, 0x00a9, 0x0110, +0x003a, 0x0183, 0x0348, 0x02f1, +0x0367, 0x02de, 0x0015, 0x01ac, +0x0339, 0x0280, 0x004b, 0x01f2, +0x0064, 0x01dd, 0x0316, 0x02af, +0x0142, 0x00fb, 0x0230, 0x0389, +0x021f, 0x03a6, 0x016d, 0x00d4, +0x0241, 0x03f8, 0x0133, 0x008a, +0x011c, 0x00a5, 0x026e, 0x03d7, +0x02fd, 0x0344, 0x018f, 0x0036, +0x01a0, 0x0019, 0x02d2, 0x036b, +0x01fe, 0x0047, 0x028c, 0x0335, +0x02a3, 0x031a, 0x01d1, 0x0068, +0x03b2, 0x020b, 0x00c0, 0x0179, +0x00ef, 0x0156, 0x039d, 0x0224, +0x00b1, 0x0108, 0x03c3, 0x027a, +0x03ec, 0x0255, 0x009e, 0x0127, +0x000d, 0x01b4, 0x037f, 0x02c6, +0x0350, 0x02e9, 0x0022, 0x019b, +0x030e, 0x02b7, 0x007c, 0x01c5, +0x0053, 0x01ea, 0x0321, 0x0298, +0x0175, 0x00cc, 0x0207, 0x03be, +0x0228, 0x0391, 0x015a, 0x00e3, +0x0276, 0x03cf, 0x0104, 0x00bd, +0x012b, 0x0092, 0x0259, 0x03e0, +0x02ca, 0x0373, 0x01b8, 0x0001, +0x0197, 0x002e, 0x02e5, 0x035c, +0x01c9, 0x0070, 0x02bb, 0x0302, +0x0294, 0x032d, 0x01e6, 0x005f, +0x03eb, 0x0252, 0x0099, 0x0120, +0x00b6, 0x010f, 0x03c4, 0x027d, +0x00e8, 0x0151, 0x039a, 0x0223, +0x03b5, 0x020c, 0x00c7, 0x017e, +0x0054, 0x01ed, 0x0326, 0x029f, +0x0309, 0x02b0, 0x007b, 0x01c2, +0x0357, 0x02ee, 0x0025, 0x019c, +0x000a, 0x01b3, 0x0378, 0x02c1, +0x012c, 0x0095, 0x025e, 0x03e7, +0x0271, 0x03c8, 0x0103, 0x00ba, +0x022f, 0x0396, 0x015d, 0x00e4, +0x0172, 0x00cb, 0x0200, 0x03b9, +0x0293, 0x032a, 0x01e1, 0x0058, +0x01ce, 0x0077, 0x02bc, 0x0305, +0x0190, 0x0029, 0x02e2, 0x035b, +0x02cd, 0x0374, 0x01bf, 0x0006, +0x03dc, 0x0265, 0x00ae, 0x0117, +0x0081, 0x0138, 0x03f3, 0x024a, +0x00df, 0x0166, 0x03ad, 0x0214, +0x0382, 0x023b, 0x00f0, 0x0149, +0x0063, 0x01da, 0x0311, 0x02a8, +0x033e, 0x0287, 0x004c, 0x01f5, +0x0360, 0x02d9, 0x0012, 0x01ab, +0x003d, 0x0184, 0x034f, 0x02f6, +0x011b, 0x00a2, 0x0269, 0x03d0, +0x0246, 0x03ff, 0x0134, 0x008d, +0x0218, 0x03a1, 0x016a, 0x00d3, +0x0145, 0x00fc, 0x0237, 0x038e, +0x02a4, 0x031d, 0x01d6, 0x006f, +0x01f9, 0x0040, 0x028b, 0x0332, +0x01a7, 0x001e, 0x02d5, 0x036c, +0x02fa, 0x0343, 0x0188, 0x0031, +0x02e1, 0x0358, 0x0193, 0x002a, +0x01bc, 0x0005, 0x02ce, 0x0377, +0x01e2, 0x005b, 0x0290, 0x0329, +0x02bf, 0x0306, 0x01cd, 0x0074, +0x015e, 0x00e7, 0x022c, 0x0395, +0x0203, 0x03ba, 0x0171, 0x00c8, +0x025d, 0x03e4, 0x012f, 0x0096, +0x0100, 0x00b9, 0x0272, 0x03cb, +0x0026, 0x019f, 0x0354, 0x02ed, +0x037b, 0x02c2, 0x0009, 0x01b0, +0x0325, 0x029c, 0x0057, 0x01ee, +0x0078, 0x01c1, 0x030a, 0x02b3, +0x0399, 0x0220, 0x00eb, 0x0152, +0x00c4, 0x017d, 0x03b6, 0x020f, +0x009a, 0x0123, 0x03e8, 0x0251, +0x03c7, 0x027e, 0x00b5, 0x010c, +0x02d6, 0x036f, 0x01a4, 0x001d, +0x018b, 0x0032, 0x02f9, 0x0340, +0x01d5, 0x006c, 0x02a7, 0x031e, +0x0288, 0x0331, 0x01fa, 0x0043, +0x0169, 0x00d0, 0x021b, 0x03a2, +0x0234, 0x038d, 0x0146, 0x00ff, +0x026a, 0x03d3, 0x0118, 0x00a1, +0x0137, 0x008e, 0x0245, 0x03fc, +0x0011, 0x01a8, 0x0363, 0x02da, +0x034c, 0x02f5, 0x003e, 0x0187, +0x0312, 0x02ab, 0x0060, 0x01d9, +0x004f, 0x01f6, 0x033d, 0x0284, +0x03ae, 0x0217, 0x00dc, 0x0165, +0x00f3, 0x014a, 0x0381, 0x0238, +0x00ad, 0x0114, 0x03df, 0x0266, +0x03f0, 0x0249, 0x0082, 0x013b, +0x028f, 0x0336, 0x01fd, 0x0044, +0x01d2, 0x006b, 0x02a0, 0x0319, +0x018c, 0x0035, 0x02fe, 0x0347, +0x02d1, 0x0368, 0x01a3, 0x001a, +0x0130, 0x0089, 0x0242, 0x03fb, +0x026d, 0x03d4, 0x011f, 0x00a6, +0x0233, 0x038a, 0x0141, 0x00f8, +0x016e, 0x00d7, 0x021c, 0x03a5, +0x0048, 0x01f1, 0x033a, 0x0283, +0x0315, 0x02ac, 0x0067, 0x01de, +0x034b, 0x02f2, 0x0039, 0x0180, +0x0016, 0x01af, 0x0364, 0x02dd, +0x03f7, 0x024e, 0x0085, 0x013c, +0x00aa, 0x0113, 0x03d8, 0x0261, +0x00f4, 0x014d, 0x0386, 0x023f, +0x03a9, 0x0210, 0x00db, 0x0162, +0x02b8, 0x0301, 0x01ca, 0x0073, +0x01e5, 0x005c, 0x0297, 0x032e, +0x01bb, 0x0002, 0x02c9, 0x0370, +0x02e6, 0x035f, 0x0194, 0x002d, +0x0107, 0x00be, 0x0275, 0x03cc, +0x025a, 0x03e3, 0x0128, 0x0091, +0x0204, 0x03bd, 0x0176, 0x00cf, +0x0159, 0x00e0, 0x022b, 0x0392, +0x007f, 0x01c6, 0x030d, 0x02b4, +0x0322, 0x029b, 0x0050, 0x01e9, +0x037c, 0x02c5, 0x000e, 0x01b7, +0x0021, 0x0198, 0x0353, 0x02ea, +0x03c0, 0x0279, 0x00b2, 0x010b, +0x009d, 0x0124, 0x03ef, 0x0256, +0x00c3, 0x017a, 0x03b1, 0x0208, +0x039e, 0x0227, 0x00ec, 0x0155, +0x023d, 0x0384, 0x014f, 0x00f6, +0x0160, 0x00d9, 0x0212, 0x03ab, +0x013e, 0x0087, 0x024c, 0x03f5, +0x0263, 0x03da, 0x0111, 0x00a8, +0x0182, 0x003b, 0x02f0, 0x0349, +0x02df, 0x0366, 0x01ad, 0x0014, +0x0281, 0x0338, 0x01f3, 0x004a, +0x01dc, 0x0065, 0x02ae, 0x0317, +0x00fa, 0x0143, 0x0388, 0x0231, +0x03a7, 0x021e, 0x00d5, 0x016c, +0x03f9, 0x0240, 0x008b, 0x0132, +0x00a4, 0x011d, 0x03d6, 0x026f, +0x0345, 0x02fc, 0x0037, 0x018e, +0x0018, 0x01a1, 0x036a, 0x02d3, +0x0046, 0x01ff, 0x0334, 0x028d, +0x031b, 0x02a2, 0x0069, 0x01d0, +0x020a, 0x03b3, 0x0178, 0x00c1, +0x0157, 0x00ee, 0x0225, 0x039c, +0x0109, 0x00b0, 0x027b, 0x03c2, +0x0254, 0x03ed, 0x0126, 0x009f, +0x01b5, 0x000c, 0x02c7, 0x037e, +0x02e8, 0x0351, 0x019a, 0x0023, +0x02b6, 0x030f, 0x01c4, 0x007d, +0x01eb, 0x0052, 0x0299, 0x0320, +0x00cd, 0x0174, 0x03bf, 0x0206, +0x0390, 0x0229, 0x00e2, 0x015b, +0x03ce, 0x0277, 0x00bc, 0x0105, +0x0093, 0x012a, 0x03e1, 0x0258, +0x0372, 0x02cb, 0x0000, 0x01b9, +0x002f, 0x0196, 0x035d, 0x02e4, +0x0071, 0x01c8, 0x0303, 0x02ba, +0x032c, 0x0295, 0x005e, 0x01e7, +0x0253, 0x03ea, 0x0121, 0x0098, +0x010e, 0x00b7, 0x027c, 0x03c5, +0x0150, 0x00e9, 0x0222, 0x039b, +0x020d, 0x03b4, 0x017f, 0x00c6, +0x01ec, 0x0055, 0x029e, 0x0327, +0x02b1, 0x0308, 0x01c3, 0x007a, +0x02ef, 0x0356, 0x019d, 0x0024, +0x01b2, 0x000b, 0x02c0, 0x0379, +0x0094, 0x012d, 0x03e6, 0x025f, +0x03c9, 0x0270, 0x00bb, 0x0102, +0x0397, 0x022e, 0x00e5, 0x015c, +0x00ca, 0x0173, 0x03b8, 0x0201, +0x032b, 0x0292, 0x0059, 0x01e0, +0x0076, 0x01cf, 0x0304, 0x02bd, +0x0028, 0x0191, 0x035a, 0x02e3, +0x0375, 0x02cc, 0x0007, 0x01be, +0x0264, 0x03dd, 0x0116, 0x00af, +0x0139, 0x0080, 0x024b, 0x03f2, +0x0167, 0x00de, 0x0215, 0x03ac, +0x023a, 0x0383, 0x0148, 0x00f1, +0x01db, 0x0062, 0x02a9, 0x0310, +0x0286, 0x033f, 0x01f4, 0x004d, +0x02d8, 0x0361, 0x01aa, 0x0013, +0x0185, 0x003c, 0x02f7, 0x034e, +0x00a3, 0x011a, 0x03d1, 0x0268, +0x03fe, 0x0247, 0x008c, 0x0135, +0x03a0, 0x0219, 0x00d2, 0x016b, +0x00fd, 0x0144, 0x038f, 0x0236, +0x031c, 0x02a5, 0x006e, 0x01d7, +0x0041, 0x01f8, 0x0333, 0x028a, +0x001f, 0x01a6, 0x036d, 0x02d4, +0x0342, 0x02fb, 0x0030, 0x0189, +0x0029, 0x0190, 0x035b, 0x02e2, +0x0374, 0x02cd, 0x0006, 0x01bf, +0x032a, 0x0293, 0x0058, 0x01e1, +0x0077, 0x01ce, 0x0305, 0x02bc, +0x0396, 0x022f, 0x00e4, 0x015d, +0x00cb, 0x0172, 0x03b9, 0x0200, +0x0095, 0x012c, 0x03e7, 0x025e, +0x03c8, 0x0271, 0x00ba, 0x0103, +0x02ee, 0x0357, 0x019c, 0x0025, +0x01b3, 0x000a, 0x02c1, 0x0378, +0x01ed, 0x0054, 0x029f, 0x0326, +0x02b0, 0x0309, 0x01c2, 0x007b, +0x0151, 0x00e8, 0x0223, 0x039a, +0x020c, 0x03b5, 0x017e, 0x00c7, +0x0252, 0x03eb, 0x0120, 0x0099, +0x010f, 0x00b6, 0x027d, 0x03c4, +0x001e, 0x01a7, 0x036c, 0x02d5, +0x0343, 0x02fa, 0x0031, 0x0188, +0x031d, 0x02a4, 0x006f, 0x01d6, +0x0040, 0x01f9, 0x0332, 0x028b, +0x03a1, 0x0218, 0x00d3, 0x016a, +0x00fc, 0x0145, 0x038e, 0x0237, +0x00a2, 0x011b, 0x03d0, 0x0269, +0x03ff, 0x0246, 0x008d, 0x0134, +0x02d9, 0x0360, 0x01ab, 0x0012, +0x0184, 0x003d, 0x02f6, 0x034f, +0x01da, 0x0063, 0x02a8, 0x0311, +0x0287, 0x033e, 0x01f5, 0x004c, +0x0166, 0x00df, 0x0214, 0x03ad, +0x023b, 0x0382, 0x0149, 0x00f0, +0x0265, 0x03dc, 0x0117, 0x00ae, +0x0138, 0x0081, 0x024a, 0x03f3, +0x0047, 0x01fe, 0x0335, 0x028c, +0x031a, 0x02a3, 0x0068, 0x01d1, +0x0344, 0x02fd, 0x0036, 0x018f, +0x0019, 0x01a0, 0x036b, 0x02d2, +0x03f8, 0x0241, 0x008a, 0x0133, +0x00a5, 0x011c, 0x03d7, 0x026e, +0x00fb, 0x0142, 0x0389, 0x0230, +0x03a6, 0x021f, 0x00d4, 0x016d, +0x0280, 0x0339, 0x01f2, 0x004b, +0x01dd, 0x0064, 0x02af, 0x0316, +0x0183, 0x003a, 0x02f1, 0x0348, +0x02de, 0x0367, 0x01ac, 0x0015, +0x013f, 0x0086, 0x024d, 0x03f4, +0x0262, 0x03db, 0x0110, 0x00a9, +0x023c, 0x0385, 0x014e, 0x00f7, +0x0161, 0x00d8, 0x0213, 0x03aa, +0x0070, 0x01c9, 0x0302, 0x02bb, +0x032d, 0x0294, 0x005f, 0x01e6, +0x0373, 0x02ca, 0x0001, 0x01b8, +0x002e, 0x0197, 0x035c, 0x02e5, +0x03cf, 0x0276, 0x00bd, 0x0104, +0x0092, 0x012b, 0x03e0, 0x0259, +0x00cc, 0x0175, 0x03be, 0x0207, +0x0391, 0x0228, 0x00e3, 0x015a, +0x02b7, 0x030e, 0x01c5, 0x007c, +0x01ea, 0x0053, 0x0298, 0x0321, +0x01b4, 0x000d, 0x02c6, 0x037f, +0x02e9, 0x0350, 0x019b, 0x0022, +0x0108, 0x00b1, 0x027a, 0x03c3, +0x0255, 0x03ec, 0x0127, 0x009e, +0x020b, 0x03b2, 0x0179, 0x00c0, +0x0156, 0x00ef, 0x0224, 0x039d, +0x00f5, 0x014c, 0x0387, 0x023e, +0x03a8, 0x0211, 0x00da, 0x0163, +0x03f6, 0x024f, 0x0084, 0x013d, +0x00ab, 0x0112, 0x03d9, 0x0260, +0x034a, 0x02f3, 0x0038, 0x0181, +0x0017, 0x01ae, 0x0365, 0x02dc, +0x0049, 0x01f0, 0x033b, 0x0282, +0x0314, 0x02ad, 0x0066, 0x01df, +0x0232, 0x038b, 0x0140, 0x00f9, +0x016f, 0x00d6, 0x021d, 0x03a4, +0x0131, 0x0088, 0x0243, 0x03fa, +0x026c, 0x03d5, 0x011e, 0x00a7, +0x018d, 0x0034, 0x02ff, 0x0346, +0x02d0, 0x0369, 0x01a2, 0x001b, +0x028e, 0x0337, 0x01fc, 0x0045, +0x01d3, 0x006a, 0x02a1, 0x0318, +0x00c2, 0x017b, 0x03b0, 0x0209, +0x039f, 0x0226, 0x00ed, 0x0154, +0x03c1, 0x0278, 0x00b3, 0x010a, +0x009c, 0x0125, 0x03ee, 0x0257, +0x037d, 0x02c4, 0x000f, 0x01b6, +0x0020, 0x0199, 0x0352, 0x02eb, +0x007e, 0x01c7, 0x030c, 0x02b5, +0x0323, 0x029a, 0x0051, 0x01e8, +0x0205, 0x03bc, 0x0177, 0x00ce, +0x0158, 0x00e1, 0x022a, 0x0393, +0x0106, 0x00bf, 0x0274, 0x03cd, +0x025b, 0x03e2, 0x0129, 0x0090, +0x01ba, 0x0003, 0x02c8, 0x0371, +0x02e7, 0x035e, 0x0195, 0x002c, +0x02b9, 0x0300, 0x01cb, 0x0072, +0x01e4, 0x005d, 0x0296, 0x032f, +0x009b, 0x0122, 0x03e9, 0x0250, +0x03c6, 0x027f, 0x00b4, 0x010d, +0x0398, 0x0221, 0x00ea, 0x0153, +0x00c5, 0x017c, 0x03b7, 0x020e, +0x0324, 0x029d, 0x0056, 0x01ef, +0x0079, 0x01c0, 0x030b, 0x02b2, +0x0027, 0x019e, 0x0355, 0x02ec, +0x037a, 0x02c3, 0x0008, 0x01b1, +0x025c, 0x03e5, 0x012e, 0x0097, +0x0101, 0x00b8, 0x0273, 0x03ca, +0x015f, 0x00e6, 0x022d, 0x0394, +0x0202, 0x03bb, 0x0170, 0x00c9, +0x01e3, 0x005a, 0x0291, 0x0328, +0x02be, 0x0307, 0x01cc, 0x0075, +0x02e0, 0x0359, 0x0192, 0x002b, +0x01bd, 0x0004, 0x02cf, 0x0376, +0x00ac, 0x0115, 0x03de, 0x0267, +0x03f1, 0x0248, 0x0083, 0x013a, +0x03af, 0x0216, 0x00dd, 0x0164, +0x00f2, 0x014b, 0x0380, 0x0239, +0x0313, 0x02aa, 0x0061, 0x01d8, +0x004e, 0x01f7, 0x033c, 0x0285, +0x0010, 0x01a9, 0x0362, 0x02db, +0x034d, 0x02f4, 0x003f, 0x0186, +0x026b, 0x03d2, 0x0119, 0x00a0, +0x0136, 0x008f, 0x0244, 0x03fd, +0x0168, 0x00d1, 0x021a, 0x03a3, +0x0235, 0x038c, 0x0147, 0x00fe, +0x01d4, 0x006d, 0x02a6, 0x031f, +0x0289, 0x0330, 0x01fb, 0x0042, +0x02d7, 0x036e, 0x01a5, 0x001c, +0x018a, 0x0033, 0x02f8, 0x0341, +0x0191, 0x0028, 0x02e3, 0x035a, +0x02cc, 0x0375, 0x01be, 0x0007, +0x0292, 0x032b, 0x01e0, 0x0059, +0x01cf, 0x0076, 0x02bd, 0x0304, +0x022e, 0x0397, 0x015c, 0x00e5, +0x0173, 0x00ca, 0x0201, 0x03b8, +0x012d, 0x0094, 0x025f, 0x03e6, +0x0270, 0x03c9, 0x0102, 0x00bb, +0x0356, 0x02ef, 0x0024, 0x019d, +0x000b, 0x01b2, 0x0379, 0x02c0, +0x0055, 0x01ec, 0x0327, 0x029e, +0x0308, 0x02b1, 0x007a, 0x01c3, +0x00e9, 0x0150, 0x039b, 0x0222, +0x03b4, 0x020d, 0x00c6, 0x017f, +0x03ea, 0x0253, 0x0098, 0x0121, +0x00b7, 0x010e, 0x03c5, 0x027c, +0x01a6, 0x001f, 0x02d4, 0x036d, +0x02fb, 0x0342, 0x0189, 0x0030, +0x02a5, 0x031c, 0x01d7, 0x006e, +0x01f8, 0x0041, 0x028a, 0x0333, +0x0219, 0x03a0, 0x016b, 0x00d2, +0x0144, 0x00fd, 0x0236, 0x038f, +0x011a, 0x00a3, 0x0268, 0x03d1, +0x0247, 0x03fe, 0x0135, 0x008c, +0x0361, 0x02d8, 0x0013, 0x01aa, +0x003c, 0x0185, 0x034e, 0x02f7, +0x0062, 0x01db, 0x0310, 0x02a9, +0x033f, 0x0286, 0x004d, 0x01f4, +0x00de, 0x0167, 0x03ac, 0x0215, +0x0383, 0x023a, 0x00f1, 0x0148, +0x03dd, 0x0264, 0x00af, 0x0116, +0x0080, 0x0139, 0x03f2, 0x024b, +0x01ff, 0x0046, 0x028d, 0x0334, +0x02a2, 0x031b, 0x01d0, 0x0069, +0x02fc, 0x0345, 0x018e, 0x0037, +0x01a1, 0x0018, 0x02d3, 0x036a, +0x0240, 0x03f9, 0x0132, 0x008b, +0x011d, 0x00a4, 0x026f, 0x03d6, +0x0143, 0x00fa, 0x0231, 0x0388, +0x021e, 0x03a7, 0x016c, 0x00d5, +0x0338, 0x0281, 0x004a, 0x01f3, +0x0065, 0x01dc, 0x0317, 0x02ae, +0x003b, 0x0182, 0x0349, 0x02f0, +0x0366, 0x02df, 0x0014, 0x01ad, +0x0087, 0x013e, 0x03f5, 0x024c, +0x03da, 0x0263, 0x00a8, 0x0111, +0x0384, 0x023d, 0x00f6, 0x014f, +0x00d9, 0x0160, 0x03ab, 0x0212, +0x01c8, 0x0071, 0x02ba, 0x0303, +0x0295, 0x032c, 0x01e7, 0x005e, +0x02cb, 0x0372, 0x01b9, 0x0000, +0x0196, 0x002f, 0x02e4, 0x035d, +0x0277, 0x03ce, 0x0105, 0x00bc, +0x012a, 0x0093, 0x0258, 0x03e1, +0x0174, 0x00cd, 0x0206, 0x03bf, +0x0229, 0x0390, 0x015b, 0x00e2, +0x030f, 0x02b6, 0x007d, 0x01c4, +0x0052, 0x01eb, 0x0320, 0x0299, +0x000c, 0x01b5, 0x037e, 0x02c7, +0x0351, 0x02e8, 0x0023, 0x019a, +0x00b0, 0x0109, 0x03c2, 0x027b, +0x03ed, 0x0254, 0x009f, 0x0126, +0x03b3, 0x020a, 0x00c1, 0x0178, +0x00ee, 0x0157, 0x039c, 0x0225, +0x014d, 0x00f4, 0x023f, 0x0386, +0x0210, 0x03a9, 0x0162, 0x00db, +0x024e, 0x03f7, 0x013c, 0x0085, +0x0113, 0x00aa, 0x0261, 0x03d8, +0x02f2, 0x034b, 0x0180, 0x0039, +0x01af, 0x0016, 0x02dd, 0x0364, +0x01f1, 0x0048, 0x0283, 0x033a, +0x02ac, 0x0315, 0x01de, 0x0067, +0x038a, 0x0233, 0x00f8, 0x0141, +0x00d7, 0x016e, 0x03a5, 0x021c, +0x0089, 0x0130, 0x03fb, 0x0242, +0x03d4, 0x026d, 0x00a6, 0x011f, +0x0035, 0x018c, 0x0347, 0x02fe, +0x0368, 0x02d1, 0x001a, 0x01a3, +0x0336, 0x028f, 0x0044, 0x01fd, +0x006b, 0x01d2, 0x0319, 0x02a0, +0x017a, 0x00c3, 0x0208, 0x03b1, +0x0227, 0x039e, 0x0155, 0x00ec, +0x0279, 0x03c0, 0x010b, 0x00b2, +0x0124, 0x009d, 0x0256, 0x03ef, +0x02c5, 0x037c, 0x01b7, 0x000e, +0x0198, 0x0021, 0x02ea, 0x0353, +0x01c6, 0x007f, 0x02b4, 0x030d, +0x029b, 0x0322, 0x01e9, 0x0050, +0x03bd, 0x0204, 0x00cf, 0x0176, +0x00e0, 0x0159, 0x0392, 0x022b, +0x00be, 0x0107, 0x03cc, 0x0275, +0x03e3, 0x025a, 0x0091, 0x0128, +0x0002, 0x01bb, 0x0370, 0x02c9, +0x035f, 0x02e6, 0x002d, 0x0194, +0x0301, 0x02b8, 0x0073, 0x01ca, +0x005c, 0x01e5, 0x032e, 0x0297, +0x0123, 0x009a, 0x0251, 0x03e8, +0x027e, 0x03c7, 0x010c, 0x00b5, +0x0220, 0x0399, 0x0152, 0x00eb, +0x017d, 0x00c4, 0x020f, 0x03b6, +0x029c, 0x0325, 0x01ee, 0x0057, +0x01c1, 0x0078, 0x02b3, 0x030a, +0x019f, 0x0026, 0x02ed, 0x0354, +0x02c2, 0x037b, 0x01b0, 0x0009, +0x03e4, 0x025d, 0x0096, 0x012f, +0x00b9, 0x0100, 0x03cb, 0x0272, +0x00e7, 0x015e, 0x0395, 0x022c, +0x03ba, 0x0203, 0x00c8, 0x0171, +0x005b, 0x01e2, 0x0329, 0x0290, +0x0306, 0x02bf, 0x0074, 0x01cd, +0x0358, 0x02e1, 0x002a, 0x0193, +0x0005, 0x01bc, 0x0377, 0x02ce, +0x0114, 0x00ad, 0x0266, 0x03df, +0x0249, 0x03f0, 0x013b, 0x0082, +0x0217, 0x03ae, 0x0165, 0x00dc, +0x014a, 0x00f3, 0x0238, 0x0381, +0x02ab, 0x0312, 0x01d9, 0x0060, +0x01f6, 0x004f, 0x0284, 0x033d, +0x01a8, 0x0011, 0x02da, 0x0363, +0x02f5, 0x034c, 0x0187, 0x003e, +0x03d3, 0x026a, 0x00a1, 0x0118, +0x008e, 0x0137, 0x03fc, 0x0245, +0x00d0, 0x0169, 0x03a2, 0x021b, +0x038d, 0x0234, 0x00ff, 0x0146, +0x006c, 0x01d5, 0x031e, 0x02a7, +0x0331, 0x0288, 0x0043, 0x01fa, +0x036f, 0x02d6, 0x001d, 0x01a4, +0x0032, 0x018b, 0x0340, 0x02f9, +0x030b, 0x02b2, 0x0079, 0x01c0, +0x0056, 0x01ef, 0x0324, 0x029d, +0x0008, 0x01b1, 0x037a, 0x02c3, +0x0355, 0x02ec, 0x0027, 0x019e, +0x00b4, 0x010d, 0x03c6, 0x027f, +0x03e9, 0x0250, 0x009b, 0x0122, +0x03b7, 0x020e, 0x00c5, 0x017c, +0x00ea, 0x0153, 0x0398, 0x0221, +0x01cc, 0x0075, 0x02be, 0x0307, +0x0291, 0x0328, 0x01e3, 0x005a, +0x02cf, 0x0376, 0x01bd, 0x0004, +0x0192, 0x002b, 0x02e0, 0x0359, +0x0273, 0x03ca, 0x0101, 0x00b8, +0x012e, 0x0097, 0x025c, 0x03e5, +0x0170, 0x00c9, 0x0202, 0x03bb, +0x022d, 0x0394, 0x015f, 0x00e6, +0x033c, 0x0285, 0x004e, 0x01f7, +0x0061, 0x01d8, 0x0313, 0x02aa, +0x003f, 0x0186, 0x034d, 0x02f4, +0x0362, 0x02db, 0x0010, 0x01a9, +0x0083, 0x013a, 0x03f1, 0x0248, +0x03de, 0x0267, 0x00ac, 0x0115, +0x0380, 0x0239, 0x00f2, 0x014b, +0x00dd, 0x0164, 0x03af, 0x0216, +0x01fb, 0x0042, 0x0289, 0x0330, +0x02a6, 0x031f, 0x01d4, 0x006d, +0x02f8, 0x0341, 0x018a, 0x0033, +0x01a5, 0x001c, 0x02d7, 0x036e, +0x0244, 0x03fd, 0x0136, 0x008f, +0x0119, 0x00a0, 0x026b, 0x03d2, +0x0147, 0x00fe, 0x0235, 0x038c, +0x021a, 0x03a3, 0x0168, 0x00d1, +0x0365, 0x02dc, 0x0017, 0x01ae, +0x0038, 0x0181, 0x034a, 0x02f3, +0x0066, 0x01df, 0x0314, 0x02ad, +0x033b, 0x0282, 0x0049, 0x01f0, +0x00da, 0x0163, 0x03a8, 0x0211, +0x0387, 0x023e, 0x00f5, 0x014c, +0x03d9, 0x0260, 0x00ab, 0x0112, +0x0084, 0x013d, 0x03f6, 0x024f, +0x01a2, 0x001b, 0x02d0, 0x0369, +0x02ff, 0x0346, 0x018d, 0x0034, +0x02a1, 0x0318, 0x01d3, 0x006a, +0x01fc, 0x0045, 0x028e, 0x0337, +0x021d, 0x03a4, 0x016f, 0x00d6, +0x0140, 0x00f9, 0x0232, 0x038b, +0x011e, 0x00a7, 0x026c, 0x03d5, +0x0243, 0x03fa, 0x0131, 0x0088, +0x0352, 0x02eb, 0x0020, 0x0199, +0x000f, 0x01b6, 0x037d, 0x02c4, +0x0051, 0x01e8, 0x0323, 0x029a, +0x030c, 0x02b5, 0x007e, 0x01c7, +0x00ed, 0x0154, 0x039f, 0x0226, +0x03b0, 0x0209, 0x00c2, 0x017b, +0x03ee, 0x0257, 0x009c, 0x0125, +0x00b3, 0x010a, 0x03c1, 0x0278, +0x0195, 0x002c, 0x02e7, 0x035e, +0x02c8, 0x0371, 0x01ba, 0x0003, +0x0296, 0x032f, 0x01e4, 0x005d, +0x01cb, 0x0072, 0x02b9, 0x0300, +0x022a, 0x0393, 0x0158, 0x00e1, +0x0177, 0x00ce, 0x0205, 0x03bc, +0x0129, 0x0090, 0x025b, 0x03e2, +0x0274, 0x03cd, 0x0106, 0x00bf, +0x03d7, 0x026e, 0x00a5, 0x011c, +0x008a, 0x0133, 0x03f8, 0x0241, +0x00d4, 0x016d, 0x03a6, 0x021f, +0x0389, 0x0230, 0x00fb, 0x0142, +0x0068, 0x01d1, 0x031a, 0x02a3, +0x0335, 0x028c, 0x0047, 0x01fe, +0x036b, 0x02d2, 0x0019, 0x01a0, +0x0036, 0x018f, 0x0344, 0x02fd, +0x0110, 0x00a9, 0x0262, 0x03db, +0x024d, 0x03f4, 0x013f, 0x0086, +0x0213, 0x03aa, 0x0161, 0x00d8, +0x014e, 0x00f7, 0x023c, 0x0385, +0x02af, 0x0316, 0x01dd, 0x0064, +0x01f2, 0x004b, 0x0280, 0x0339, +0x01ac, 0x0015, 0x02de, 0x0367, +0x02f1, 0x0348, 0x0183, 0x003a, +0x03e0, 0x0259, 0x0092, 0x012b, +0x00bd, 0x0104, 0x03cf, 0x0276, +0x00e3, 0x015a, 0x0391, 0x0228, +0x03be, 0x0207, 0x00cc, 0x0175, +0x005f, 0x01e6, 0x032d, 0x0294, +0x0302, 0x02bb, 0x0070, 0x01c9, +0x035c, 0x02e5, 0x002e, 0x0197, +0x0001, 0x01b8, 0x0373, 0x02ca, +0x0127, 0x009e, 0x0255, 0x03ec, +0x027a, 0x03c3, 0x0108, 0x00b1, +0x0224, 0x039d, 0x0156, 0x00ef, +0x0179, 0x00c0, 0x020b, 0x03b2, +0x0298, 0x0321, 0x01ea, 0x0053, +0x01c5, 0x007c, 0x02b7, 0x030e, +0x019b, 0x0022, 0x02e9, 0x0350, +0x02c6, 0x037f, 0x01b4, 0x000d, +0x03b9, 0x0200, 0x00cb, 0x0172, +0x00e4, 0x015d, 0x0396, 0x022f, +0x00ba, 0x0103, 0x03c8, 0x0271, +0x03e7, 0x025e, 0x0095, 0x012c, +0x0006, 0x01bf, 0x0374, 0x02cd, +0x035b, 0x02e2, 0x0029, 0x0190, +0x0305, 0x02bc, 0x0077, 0x01ce, +0x0058, 0x01e1, 0x032a, 0x0293, +0x017e, 0x00c7, 0x020c, 0x03b5, +0x0223, 0x039a, 0x0151, 0x00e8, +0x027d, 0x03c4, 0x010f, 0x00b6, +0x0120, 0x0099, 0x0252, 0x03eb, +0x02c1, 0x0378, 0x01b3, 0x000a, +0x019c, 0x0025, 0x02ee, 0x0357, +0x01c2, 0x007b, 0x02b0, 0x0309, +0x029f, 0x0326, 0x01ed, 0x0054, +0x038e, 0x0237, 0x00fc, 0x0145, +0x00d3, 0x016a, 0x03a1, 0x0218, +0x008d, 0x0134, 0x03ff, 0x0246, +0x03d0, 0x0269, 0x00a2, 0x011b, +0x0031, 0x0188, 0x0343, 0x02fa, +0x036c, 0x02d5, 0x001e, 0x01a7, +0x0332, 0x028b, 0x0040, 0x01f9, +0x006f, 0x01d6, 0x031d, 0x02a4, +0x0149, 0x00f0, 0x023b, 0x0382, +0x0214, 0x03ad, 0x0166, 0x00df, +0x024a, 0x03f3, 0x0138, 0x0081, +0x0117, 0x00ae, 0x0265, 0x03dc, +0x02f6, 0x034f, 0x0184, 0x003d, +0x01ab, 0x0012, 0x02d9, 0x0360, +0x01f5, 0x004c, 0x0287, 0x033e, +0x02a8, 0x0311, 0x01da, 0x0063, +0x02b3, 0x030a, 0x01c1, 0x0078, +0x01ee, 0x0057, 0x029c, 0x0325, +0x01b0, 0x0009, 0x02c2, 0x037b, +0x02ed, 0x0354, 0x019f, 0x0026, +0x010c, 0x00b5, 0x027e, 0x03c7, +0x0251, 0x03e8, 0x0123, 0x009a, +0x020f, 0x03b6, 0x017d, 0x00c4, +0x0152, 0x00eb, 0x0220, 0x0399, +0x0074, 0x01cd, 0x0306, 0x02bf, +0x0329, 0x0290, 0x005b, 0x01e2, +0x0377, 0x02ce, 0x0005, 0x01bc, +0x002a, 0x0193, 0x0358, 0x02e1, +0x03cb, 0x0272, 0x00b9, 0x0100, +0x0096, 0x012f, 0x03e4, 0x025d, +0x00c8, 0x0171, 0x03ba, 0x0203, +0x0395, 0x022c, 0x00e7, 0x015e, +0x0284, 0x033d, 0x01f6, 0x004f, +0x01d9, 0x0060, 0x02ab, 0x0312, +0x0187, 0x003e, 0x02f5, 0x034c, +0x02da, 0x0363, 0x01a8, 0x0011, +0x013b, 0x0082, 0x0249, 0x03f0, +0x0266, 0x03df, 0x0114, 0x00ad, +0x0238, 0x0381, 0x014a, 0x00f3, +0x0165, 0x00dc, 0x0217, 0x03ae, +0x0043, 0x01fa, 0x0331, 0x0288, +0x031e, 0x02a7, 0x006c, 0x01d5, +0x0340, 0x02f9, 0x0032, 0x018b, +0x001d, 0x01a4, 0x036f, 0x02d6, +0x03fc, 0x0245, 0x008e, 0x0137, +0x00a1, 0x0118, 0x03d3, 0x026a, +0x00ff, 0x0146, 0x038d, 0x0234, +0x03a2, 0x021b, 0x00d0, 0x0169, +0x02dd, 0x0364, 0x01af, 0x0016, +0x0180, 0x0039, 0x02f2, 0x034b, +0x01de, 0x0067, 0x02ac, 0x0315, +0x0283, 0x033a, 0x01f1, 0x0048, +0x0162, 0x00db, 0x0210, 0x03a9, +0x023f, 0x0386, 0x014d, 0x00f4, +0x0261, 0x03d8, 0x0113, 0x00aa, +0x013c, 0x0085, 0x024e, 0x03f7, +0x001a, 0x01a3, 0x0368, 0x02d1, +0x0347, 0x02fe, 0x0035, 0x018c, +0x0319, 0x02a0, 0x006b, 0x01d2, +0x0044, 0x01fd, 0x0336, 0x028f, +0x03a5, 0x021c, 0x00d7, 0x016e, +0x00f8, 0x0141, 0x038a, 0x0233, +0x00a6, 0x011f, 0x03d4, 0x026d, +0x03fb, 0x0242, 0x0089, 0x0130, +0x02ea, 0x0353, 0x0198, 0x0021, +0x01b7, 0x000e, 0x02c5, 0x037c, +0x01e9, 0x0050, 0x029b, 0x0322, +0x02b4, 0x030d, 0x01c6, 0x007f, +0x0155, 0x00ec, 0x0227, 0x039e, +0x0208, 0x03b1, 0x017a, 0x00c3, +0x0256, 0x03ef, 0x0124, 0x009d, +0x010b, 0x00b2, 0x0279, 0x03c0, +0x002d, 0x0194, 0x035f, 0x02e6, +0x0370, 0x02c9, 0x0002, 0x01bb, +0x032e, 0x0297, 0x005c, 0x01e5, +0x0073, 0x01ca, 0x0301, 0x02b8, +0x0392, 0x022b, 0x00e0, 0x0159, +0x00cf, 0x0176, 0x03bd, 0x0204, +0x0091, 0x0128, 0x03e3, 0x025a, +0x03cc, 0x0275, 0x00be, 0x0107, +0x026f, 0x03d6, 0x011d, 0x00a4, +0x0132, 0x008b, 0x0240, 0x03f9, +0x016c, 0x00d5, 0x021e, 0x03a7, +0x0231, 0x0388, 0x0143, 0x00fa, +0x01d0, 0x0069, 0x02a2, 0x031b, +0x028d, 0x0334, 0x01ff, 0x0046, +0x02d3, 0x036a, 0x01a1, 0x0018, +0x018e, 0x0037, 0x02fc, 0x0345, +0x00a8, 0x0111, 0x03da, 0x0263, +0x03f5, 0x024c, 0x0087, 0x013e, +0x03ab, 0x0212, 0x00d9, 0x0160, +0x00f6, 0x014f, 0x0384, 0x023d, +0x0317, 0x02ae, 0x0065, 0x01dc, +0x004a, 0x01f3, 0x0338, 0x0281, +0x0014, 0x01ad, 0x0366, 0x02df, +0x0349, 0x02f0, 0x003b, 0x0182, +0x0258, 0x03e1, 0x012a, 0x0093, +0x0105, 0x00bc, 0x0277, 0x03ce, +0x015b, 0x00e2, 0x0229, 0x0390, +0x0206, 0x03bf, 0x0174, 0x00cd, +0x01e7, 0x005e, 0x0295, 0x032c, +0x02ba, 0x0303, 0x01c8, 0x0071, +0x02e4, 0x035d, 0x0196, 0x002f, +0x01b9, 0x0000, 0x02cb, 0x0372, +0x009f, 0x0126, 0x03ed, 0x0254, +0x03c2, 0x027b, 0x00b0, 0x0109, +0x039c, 0x0225, 0x00ee, 0x0157, +0x00c1, 0x0178, 0x03b3, 0x020a, +0x0320, 0x0299, 0x0052, 0x01eb, +0x007d, 0x01c4, 0x030f, 0x02b6, +0x0023, 0x019a, 0x0351, 0x02e8, +0x037e, 0x02c7, 0x000c, 0x01b5, +0x0201, 0x03b8, 0x0173, 0x00ca, +0x015c, 0x00e5, 0x022e, 0x0397, +0x0102, 0x00bb, 0x0270, 0x03c9, +0x025f, 0x03e6, 0x012d, 0x0094, +0x01be, 0x0007, 0x02cc, 0x0375, +0x02e3, 0x035a, 0x0191, 0x0028, +0x02bd, 0x0304, 0x01cf, 0x0076, +0x01e0, 0x0059, 0x0292, 0x032b, +0x00c6, 0x017f, 0x03b4, 0x020d, +0x039b, 0x0222, 0x00e9, 0x0150, +0x03c5, 0x027c, 0x00b7, 0x010e, +0x0098, 0x0121, 0x03ea, 0x0253, +0x0379, 0x02c0, 0x000b, 0x01b2, +0x0024, 0x019d, 0x0356, 0x02ef, +0x007a, 0x01c3, 0x0308, 0x02b1, +0x0327, 0x029e, 0x0055, 0x01ec, +0x0236, 0x038f, 0x0144, 0x00fd, +0x016b, 0x00d2, 0x0219, 0x03a0, +0x0135, 0x008c, 0x0247, 0x03fe, +0x0268, 0x03d1, 0x011a, 0x00a3, +0x0189, 0x0030, 0x02fb, 0x0342, +0x02d4, 0x036d, 0x01a6, 0x001f, +0x028a, 0x0333, 0x01f8, 0x0041, +0x01d7, 0x006e, 0x02a5, 0x031c, +0x00f1, 0x0148, 0x0383, 0x023a, +0x03ac, 0x0215, 0x00de, 0x0167, +0x03f2, 0x024b, 0x0080, 0x0139, +0x00af, 0x0116, 0x03dd, 0x0264, +0x034e, 0x02f7, 0x003c, 0x0185, +0x0013, 0x01aa, 0x0361, 0x02d8, +0x004d, 0x01f4, 0x033f, 0x0286, +0x0310, 0x02a9, 0x0062, 0x01db, +0x007b, 0x01c2, 0x0309, 0x02b0, +0x0326, 0x029f, 0x0054, 0x01ed, +0x0378, 0x02c1, 0x000a, 0x01b3, +0x0025, 0x019c, 0x0357, 0x02ee, +0x03c4, 0x027d, 0x00b6, 0x010f, +0x0099, 0x0120, 0x03eb, 0x0252, +0x00c7, 0x017e, 0x03b5, 0x020c, +0x039a, 0x0223, 0x00e8, 0x0151, +0x02bc, 0x0305, 0x01ce, 0x0077, +0x01e1, 0x0058, 0x0293, 0x032a, +0x01bf, 0x0006, 0x02cd, 0x0374, +0x02e2, 0x035b, 0x0190, 0x0029, +0x0103, 0x00ba, 0x0271, 0x03c8, +0x025e, 0x03e7, 0x012c, 0x0095, +0x0200, 0x03b9, 0x0172, 0x00cb, +0x015d, 0x00e4, 0x022f, 0x0396, +0x004c, 0x01f5, 0x033e, 0x0287, +0x0311, 0x02a8, 0x0063, 0x01da, +0x034f, 0x02f6, 0x003d, 0x0184, +0x0012, 0x01ab, 0x0360, 0x02d9, +0x03f3, 0x024a, 0x0081, 0x0138, +0x00ae, 0x0117, 0x03dc, 0x0265, +0x00f0, 0x0149, 0x0382, 0x023b, +0x03ad, 0x0214, 0x00df, 0x0166, +0x028b, 0x0332, 0x01f9, 0x0040, +0x01d6, 0x006f, 0x02a4, 0x031d, +0x0188, 0x0031, 0x02fa, 0x0343, +0x02d5, 0x036c, 0x01a7, 0x001e, +0x0134, 0x008d, 0x0246, 0x03ff, +0x0269, 0x03d0, 0x011b, 0x00a2, +0x0237, 0x038e, 0x0145, 0x00fc, +0x016a, 0x00d3, 0x0218, 0x03a1, +0x0015, 0x01ac, 0x0367, 0x02de, +0x0348, 0x02f1, 0x003a, 0x0183, +0x0316, 0x02af, 0x0064, 0x01dd, +0x004b, 0x01f2, 0x0339, 0x0280, +0x03aa, 0x0213, 0x00d8, 0x0161, +0x00f7, 0x014e, 0x0385, 0x023c, +0x00a9, 0x0110, 0x03db, 0x0262, +0x03f4, 0x024d, 0x0086, 0x013f, +0x02d2, 0x036b, 0x01a0, 0x0019, +0x018f, 0x0036, 0x02fd, 0x0344, +0x01d1, 0x0068, 0x02a3, 0x031a, +0x028c, 0x0335, 0x01fe, 0x0047, +0x016d, 0x00d4, 0x021f, 0x03a6, +0x0230, 0x0389, 0x0142, 0x00fb, +0x026e, 0x03d7, 0x011c, 0x00a5, +0x0133, 0x008a, 0x0241, 0x03f8, +0x0022, 0x019b, 0x0350, 0x02e9, +0x037f, 0x02c6, 0x000d, 0x01b4, +0x0321, 0x0298, 0x0053, 0x01ea, +0x007c, 0x01c5, 0x030e, 0x02b7, +0x039d, 0x0224, 0x00ef, 0x0156, +0x00c0, 0x0179, 0x03b2, 0x020b, +0x009e, 0x0127, 0x03ec, 0x0255, +0x03c3, 0x027a, 0x00b1, 0x0108, +0x02e5, 0x035c, 0x0197, 0x002e, +0x01b8, 0x0001, 0x02ca, 0x0373, +0x01e6, 0x005f, 0x0294, 0x032d, +0x02bb, 0x0302, 0x01c9, 0x0070, +0x015a, 0x00e3, 0x0228, 0x0391, +0x0207, 0x03be, 0x0175, 0x00cc, +0x0259, 0x03e0, 0x012b, 0x0092, +0x0104, 0x00bd, 0x0276, 0x03cf, +0x00a7, 0x011e, 0x03d5, 0x026c, +0x03fa, 0x0243, 0x0088, 0x0131, +0x03a4, 0x021d, 0x00d6, 0x016f, +0x00f9, 0x0140, 0x038b, 0x0232, +0x0318, 0x02a1, 0x006a, 0x01d3, +0x0045, 0x01fc, 0x0337, 0x028e, +0x001b, 0x01a2, 0x0369, 0x02d0, +0x0346, 0x02ff, 0x0034, 0x018d, +0x0260, 0x03d9, 0x0112, 0x00ab, +0x013d, 0x0084, 0x024f, 0x03f6, +0x0163, 0x00da, 0x0211, 0x03a8, +0x023e, 0x0387, 0x014c, 0x00f5, +0x01df, 0x0066, 0x02ad, 0x0314, +0x0282, 0x033b, 0x01f0, 0x0049, +0x02dc, 0x0365, 0x01ae, 0x0017, +0x0181, 0x0038, 0x02f3, 0x034a, +0x0090, 0x0129, 0x03e2, 0x025b, +0x03cd, 0x0274, 0x00bf, 0x0106, +0x0393, 0x022a, 0x00e1, 0x0158, +0x00ce, 0x0177, 0x03bc, 0x0205, +0x032f, 0x0296, 0x005d, 0x01e4, +0x0072, 0x01cb, 0x0300, 0x02b9, +0x002c, 0x0195, 0x035e, 0x02e7, +0x0371, 0x02c8, 0x0003, 0x01ba, +0x0257, 0x03ee, 0x0125, 0x009c, +0x010a, 0x00b3, 0x0278, 0x03c1, +0x0154, 0x00ed, 0x0226, 0x039f, +0x0209, 0x03b0, 0x017b, 0x00c2, +0x01e8, 0x0051, 0x029a, 0x0323, +0x02b5, 0x030c, 0x01c7, 0x007e, +0x02eb, 0x0352, 0x0199, 0x0020, +0x01b6, 0x000f, 0x02c4, 0x037d, +0x00c9, 0x0170, 0x03bb, 0x0202, +0x0394, 0x022d, 0x00e6, 0x015f, +0x03ca, 0x0273, 0x00b8, 0x0101, +0x0097, 0x012e, 0x03e5, 0x025c, +0x0376, 0x02cf, 0x0004, 0x01bd, +0x002b, 0x0192, 0x0359, 0x02e0, +0x0075, 0x01cc, 0x0307, 0x02be, +0x0328, 0x0291, 0x005a, 0x01e3, +0x020e, 0x03b7, 0x017c, 0x00c5, +0x0153, 0x00ea, 0x0221, 0x0398, +0x010d, 0x00b4, 0x027f, 0x03c6, +0x0250, 0x03e9, 0x0122, 0x009b, +0x01b1, 0x0008, 0x02c3, 0x037a, +0x02ec, 0x0355, 0x019e, 0x0027, +0x02b2, 0x030b, 0x01c0, 0x0079, +0x01ef, 0x0056, 0x029d, 0x0324, +0x00fe, 0x0147, 0x038c, 0x0235, +0x03a3, 0x021a, 0x00d1, 0x0168, +0x03fd, 0x0244, 0x008f, 0x0136, +0x00a0, 0x0119, 0x03d2, 0x026b, +0x0341, 0x02f8, 0x0033, 0x018a, +0x001c, 0x01a5, 0x036e, 0x02d7, +0x0042, 0x01fb, 0x0330, 0x0289, +0x031f, 0x02a6, 0x006d, 0x01d4, +0x0239, 0x0380, 0x014b, 0x00f2, +0x0164, 0x00dd, 0x0216, 0x03af, +0x013a, 0x0083, 0x0248, 0x03f1, +0x0267, 0x03de, 0x0115, 0x00ac, +0x0186, 0x003f, 0x02f4, 0x034d, +0x02db, 0x0362, 0x01a9, 0x0010, +0x0285, 0x033c, 0x01f7, 0x004e, +0x01d8, 0x0061, 0x02aa, 0x0313, +0x01c3, 0x007a, 0x02b1, 0x0308, +0x029e, 0x0327, 0x01ec, 0x0055, +0x02c0, 0x0379, 0x01b2, 0x000b, +0x019d, 0x0024, 0x02ef, 0x0356, +0x027c, 0x03c5, 0x010e, 0x00b7, +0x0121, 0x0098, 0x0253, 0x03ea, +0x017f, 0x00c6, 0x020d, 0x03b4, +0x0222, 0x039b, 0x0150, 0x00e9, +0x0304, 0x02bd, 0x0076, 0x01cf, +0x0059, 0x01e0, 0x032b, 0x0292, +0x0007, 0x01be, 0x0375, 0x02cc, +0x035a, 0x02e3, 0x0028, 0x0191, +0x00bb, 0x0102, 0x03c9, 0x0270, +0x03e6, 0x025f, 0x0094, 0x012d, +0x03b8, 0x0201, 0x00ca, 0x0173, +0x00e5, 0x015c, 0x0397, 0x022e, +0x01f4, 0x004d, 0x0286, 0x033f, +0x02a9, 0x0310, 0x01db, 0x0062, +0x02f7, 0x034e, 0x0185, 0x003c, +0x01aa, 0x0013, 0x02d8, 0x0361, +0x024b, 0x03f2, 0x0139, 0x0080, +0x0116, 0x00af, 0x0264, 0x03dd, +0x0148, 0x00f1, 0x023a, 0x0383, +0x0215, 0x03ac, 0x0167, 0x00de, +0x0333, 0x028a, 0x0041, 0x01f8, +0x006e, 0x01d7, 0x031c, 0x02a5, +0x0030, 0x0189, 0x0342, 0x02fb, +0x036d, 0x02d4, 0x001f, 0x01a6, +0x008c, 0x0135, 0x03fe, 0x0247, +0x03d1, 0x0268, 0x00a3, 0x011a, +0x038f, 0x0236, 0x00fd, 0x0144, +0x00d2, 0x016b, 0x03a0, 0x0219, +0x01ad, 0x0014, 0x02df, 0x0366, +0x02f0, 0x0349, 0x0182, 0x003b, +0x02ae, 0x0317, 0x01dc, 0x0065, +0x01f3, 0x004a, 0x0281, 0x0338, +0x0212, 0x03ab, 0x0160, 0x00d9, +0x014f, 0x00f6, 0x023d, 0x0384, +0x0111, 0x00a8, 0x0263, 0x03da, +0x024c, 0x03f5, 0x013e, 0x0087, +0x036a, 0x02d3, 0x0018, 0x01a1, +0x0037, 0x018e, 0x0345, 0x02fc, +0x0069, 0x01d0, 0x031b, 0x02a2, +0x0334, 0x028d, 0x0046, 0x01ff, +0x00d5, 0x016c, 0x03a7, 0x021e, +0x0388, 0x0231, 0x00fa, 0x0143, +0x03d6, 0x026f, 0x00a4, 0x011d, +0x008b, 0x0132, 0x03f9, 0x0240, +0x019a, 0x0023, 0x02e8, 0x0351, +0x02c7, 0x037e, 0x01b5, 0x000c, +0x0299, 0x0320, 0x01eb, 0x0052, +0x01c4, 0x007d, 0x02b6, 0x030f, +0x0225, 0x039c, 0x0157, 0x00ee, +0x0178, 0x00c1, 0x020a, 0x03b3, +0x0126, 0x009f, 0x0254, 0x03ed, +0x027b, 0x03c2, 0x0109, 0x00b0, +0x035d, 0x02e4, 0x002f, 0x0196, +0x0000, 0x01b9, 0x0372, 0x02cb, +0x005e, 0x01e7, 0x032c, 0x0295, +0x0303, 0x02ba, 0x0071, 0x01c8, +0x00e2, 0x015b, 0x0390, 0x0229, +0x03bf, 0x0206, 0x00cd, 0x0174, +0x03e1, 0x0258, 0x0093, 0x012a, +0x00bc, 0x0105, 0x03ce, 0x0277, +0x011f, 0x00a6, 0x026d, 0x03d4, +0x0242, 0x03fb, 0x0130, 0x0089, +0x021c, 0x03a5, 0x016e, 0x00d7, +0x0141, 0x00f8, 0x0233, 0x038a, +0x02a0, 0x0319, 0x01d2, 0x006b, +0x01fd, 0x0044, 0x028f, 0x0336, +0x01a3, 0x001a, 0x02d1, 0x0368, +0x02fe, 0x0347, 0x018c, 0x0035, +0x03d8, 0x0261, 0x00aa, 0x0113, +0x0085, 0x013c, 0x03f7, 0x024e, +0x00db, 0x0162, 0x03a9, 0x0210, +0x0386, 0x023f, 0x00f4, 0x014d, +0x0067, 0x01de, 0x0315, 0x02ac, +0x033a, 0x0283, 0x0048, 0x01f1, +0x0364, 0x02dd, 0x0016, 0x01af, +0x0039, 0x0180, 0x034b, 0x02f2, +0x0128, 0x0091, 0x025a, 0x03e3, +0x0275, 0x03cc, 0x0107, 0x00be, +0x022b, 0x0392, 0x0159, 0x00e0, +0x0176, 0x00cf, 0x0204, 0x03bd, +0x0297, 0x032e, 0x01e5, 0x005c, +0x01ca, 0x0073, 0x02b8, 0x0301, +0x0194, 0x002d, 0x02e6, 0x035f, +0x02c9, 0x0370, 0x01bb, 0x0002, +0x03ef, 0x0256, 0x009d, 0x0124, +0x00b2, 0x010b, 0x03c0, 0x0279, +0x00ec, 0x0155, 0x039e, 0x0227, +0x03b1, 0x0208, 0x00c3, 0x017a, +0x0050, 0x01e9, 0x0322, 0x029b, +0x030d, 0x02b4, 0x007f, 0x01c6, +0x0353, 0x02ea, 0x0021, 0x0198, +0x000e, 0x01b7, 0x037c, 0x02c5, +0x0171, 0x00c8, 0x0203, 0x03ba, +0x022c, 0x0395, 0x015e, 0x00e7, +0x0272, 0x03cb, 0x0100, 0x00b9, +0x012f, 0x0096, 0x025d, 0x03e4, +0x02ce, 0x0377, 0x01bc, 0x0005, +0x0193, 0x002a, 0x02e1, 0x0358, +0x01cd, 0x0074, 0x02bf, 0x0306, +0x0290, 0x0329, 0x01e2, 0x005b, +0x03b6, 0x020f, 0x00c4, 0x017d, +0x00eb, 0x0152, 0x0399, 0x0220, +0x00b5, 0x010c, 0x03c7, 0x027e, +0x03e8, 0x0251, 0x009a, 0x0123, +0x0009, 0x01b0, 0x037b, 0x02c2, +0x0354, 0x02ed, 0x0026, 0x019f, +0x030a, 0x02b3, 0x0078, 0x01c1, +0x0057, 0x01ee, 0x0325, 0x029c, +0x0146, 0x00ff, 0x0234, 0x038d, +0x021b, 0x03a2, 0x0169, 0x00d0, +0x0245, 0x03fc, 0x0137, 0x008e, +0x0118, 0x00a1, 0x026a, 0x03d3, +0x02f9, 0x0340, 0x018b, 0x0032, +0x01a4, 0x001d, 0x02d6, 0x036f, +0x01fa, 0x0043, 0x0288, 0x0331, +0x02a7, 0x031e, 0x01d5, 0x006c, +0x0381, 0x0238, 0x00f3, 0x014a, +0x00dc, 0x0165, 0x03ae, 0x0217, +0x0082, 0x013b, 0x03f0, 0x0249, +0x03df, 0x0266, 0x00ad, 0x0114, +0x003e, 0x0187, 0x034c, 0x02f5, +0x0363, 0x02da, 0x0011, 0x01a8, +0x033d, 0x0284, 0x004f, 0x01f6, +0x0060, 0x01d9, 0x0312, 0x02ab, +0x0052, 0x01eb, 0x0320, 0x0299, +0x030f, 0x02b6, 0x007d, 0x01c4, +0x0351, 0x02e8, 0x0023, 0x019a, +0x000c, 0x01b5, 0x037e, 0x02c7, +0x03ed, 0x0254, 0x009f, 0x0126, +0x00b0, 0x0109, 0x03c2, 0x027b, +0x00ee, 0x0157, 0x039c, 0x0225, +0x03b3, 0x020a, 0x00c1, 0x0178, +0x0295, 0x032c, 0x01e7, 0x005e, +0x01c8, 0x0071, 0x02ba, 0x0303, +0x0196, 0x002f, 0x02e4, 0x035d, +0x02cb, 0x0372, 0x01b9, 0x0000, +0x012a, 0x0093, 0x0258, 0x03e1, +0x0277, 0x03ce, 0x0105, 0x00bc, +0x0229, 0x0390, 0x015b, 0x00e2, +0x0174, 0x00cd, 0x0206, 0x03bf, +0x0065, 0x01dc, 0x0317, 0x02ae, +0x0338, 0x0281, 0x004a, 0x01f3, +0x0366, 0x02df, 0x0014, 0x01ad, +0x003b, 0x0182, 0x0349, 0x02f0, +0x03da, 0x0263, 0x00a8, 0x0111, +0x0087, 0x013e, 0x03f5, 0x024c, +0x00d9, 0x0160, 0x03ab, 0x0212, +0x0384, 0x023d, 0x00f6, 0x014f, +0x02a2, 0x031b, 0x01d0, 0x0069, +0x01ff, 0x0046, 0x028d, 0x0334, +0x01a1, 0x0018, 0x02d3, 0x036a, +0x02fc, 0x0345, 0x018e, 0x0037, +0x011d, 0x00a4, 0x026f, 0x03d6, +0x0240, 0x03f9, 0x0132, 0x008b, +0x021e, 0x03a7, 0x016c, 0x00d5, +0x0143, 0x00fa, 0x0231, 0x0388, +0x003c, 0x0185, 0x034e, 0x02f7, +0x0361, 0x02d8, 0x0013, 0x01aa, +0x033f, 0x0286, 0x004d, 0x01f4, +0x0062, 0x01db, 0x0310, 0x02a9, +0x0383, 0x023a, 0x00f1, 0x0148, +0x00de, 0x0167, 0x03ac, 0x0215, +0x0080, 0x0139, 0x03f2, 0x024b, +0x03dd, 0x0264, 0x00af, 0x0116, +0x02fb, 0x0342, 0x0189, 0x0030, +0x01a6, 0x001f, 0x02d4, 0x036d, +0x01f8, 0x0041, 0x028a, 0x0333, +0x02a5, 0x031c, 0x01d7, 0x006e, +0x0144, 0x00fd, 0x0236, 0x038f, +0x0219, 0x03a0, 0x016b, 0x00d2, +0x0247, 0x03fe, 0x0135, 0x008c, +0x011a, 0x00a3, 0x0268, 0x03d1, +0x000b, 0x01b2, 0x0379, 0x02c0, +0x0356, 0x02ef, 0x0024, 0x019d, +0x0308, 0x02b1, 0x007a, 0x01c3, +0x0055, 0x01ec, 0x0327, 0x029e, +0x03b4, 0x020d, 0x00c6, 0x017f, +0x00e9, 0x0150, 0x039b, 0x0222, +0x00b7, 0x010e, 0x03c5, 0x027c, +0x03ea, 0x0253, 0x0098, 0x0121, +0x02cc, 0x0375, 0x01be, 0x0007, +0x0191, 0x0028, 0x02e3, 0x035a, +0x01cf, 0x0076, 0x02bd, 0x0304, +0x0292, 0x032b, 0x01e0, 0x0059, +0x0173, 0x00ca, 0x0201, 0x03b8, +0x022e, 0x0397, 0x015c, 0x00e5, +0x0270, 0x03c9, 0x0102, 0x00bb, +0x012d, 0x0094, 0x025f, 0x03e6, +0x008e, 0x0137, 0x03fc, 0x0245, +0x03d3, 0x026a, 0x00a1, 0x0118, +0x038d, 0x0234, 0x00ff, 0x0146, +0x00d0, 0x0169, 0x03a2, 0x021b, +0x0331, 0x0288, 0x0043, 0x01fa, +0x006c, 0x01d5, 0x031e, 0x02a7, +0x0032, 0x018b, 0x0340, 0x02f9, +0x036f, 0x02d6, 0x001d, 0x01a4, +0x0249, 0x03f0, 0x013b, 0x0082, +0x0114, 0x00ad, 0x0266, 0x03df, +0x014a, 0x00f3, 0x0238, 0x0381, +0x0217, 0x03ae, 0x0165, 0x00dc, +0x01f6, 0x004f, 0x0284, 0x033d, +0x02ab, 0x0312, 0x01d9, 0x0060, +0x02f5, 0x034c, 0x0187, 0x003e, +0x01a8, 0x0011, 0x02da, 0x0363, +0x00b9, 0x0100, 0x03cb, 0x0272, +0x03e4, 0x025d, 0x0096, 0x012f, +0x03ba, 0x0203, 0x00c8, 0x0171, +0x00e7, 0x015e, 0x0395, 0x022c, +0x0306, 0x02bf, 0x0074, 0x01cd, +0x005b, 0x01e2, 0x0329, 0x0290, +0x0005, 0x01bc, 0x0377, 0x02ce, +0x0358, 0x02e1, 0x002a, 0x0193, +0x027e, 0x03c7, 0x010c, 0x00b5, +0x0123, 0x009a, 0x0251, 0x03e8, +0x017d, 0x00c4, 0x020f, 0x03b6, +0x0220, 0x0399, 0x0152, 0x00eb, +0x01c1, 0x0078, 0x02b3, 0x030a, +0x029c, 0x0325, 0x01ee, 0x0057, +0x02c2, 0x037b, 0x01b0, 0x0009, +0x019f, 0x0026, 0x02ed, 0x0354, +0x00e0, 0x0159, 0x0392, 0x022b, +0x03bd, 0x0204, 0x00cf, 0x0176, +0x03e3, 0x025a, 0x0091, 0x0128, +0x00be, 0x0107, 0x03cc, 0x0275, +0x035f, 0x02e6, 0x002d, 0x0194, +0x0002, 0x01bb, 0x0370, 0x02c9, +0x005c, 0x01e5, 0x032e, 0x0297, +0x0301, 0x02b8, 0x0073, 0x01ca, +0x0227, 0x039e, 0x0155, 0x00ec, +0x017a, 0x00c3, 0x0208, 0x03b1, +0x0124, 0x009d, 0x0256, 0x03ef, +0x0279, 0x03c0, 0x010b, 0x00b2, +0x0198, 0x0021, 0x02ea, 0x0353, +0x02c5, 0x037c, 0x01b7, 0x000e, +0x029b, 0x0322, 0x01e9, 0x0050, +0x01c6, 0x007f, 0x02b4, 0x030d, +0x00d7, 0x016e, 0x03a5, 0x021c, +0x038a, 0x0233, 0x00f8, 0x0141, +0x03d4, 0x026d, 0x00a6, 0x011f, +0x0089, 0x0130, 0x03fb, 0x0242, +0x0368, 0x02d1, 0x001a, 0x01a3, +0x0035, 0x018c, 0x0347, 0x02fe, +0x006b, 0x01d2, 0x0319, 0x02a0, +0x0336, 0x028f, 0x0044, 0x01fd, +0x0210, 0x03a9, 0x0162, 0x00db, +0x014d, 0x00f4, 0x023f, 0x0386, +0x0113, 0x00aa, 0x0261, 0x03d8, +0x024e, 0x03f7, 0x013c, 0x0085, +0x01af, 0x0016, 0x02dd, 0x0364, +0x02f2, 0x034b, 0x0180, 0x0039, +0x02ac, 0x0315, 0x01de, 0x0067, +0x01f1, 0x0048, 0x0283, 0x033a, +0x01ea, 0x0053, 0x0298, 0x0321, +0x02b7, 0x030e, 0x01c5, 0x007c, +0x02e9, 0x0350, 0x019b, 0x0022, +0x01b4, 0x000d, 0x02c6, 0x037f, +0x0255, 0x03ec, 0x0127, 0x009e, +0x0108, 0x00b1, 0x027a, 0x03c3, +0x0156, 0x00ef, 0x0224, 0x039d, +0x020b, 0x03b2, 0x0179, 0x00c0, +0x032d, 0x0294, 0x005f, 0x01e6, +0x0070, 0x01c9, 0x0302, 0x02bb, +0x002e, 0x0197, 0x035c, 0x02e5, +0x0373, 0x02ca, 0x0001, 0x01b8, +0x0092, 0x012b, 0x03e0, 0x0259, +0x03cf, 0x0276, 0x00bd, 0x0104, +0x0391, 0x0228, 0x00e3, 0x015a, +0x00cc, 0x0175, 0x03be, 0x0207, +0x01dd, 0x0064, 0x02af, 0x0316, +0x0280, 0x0339, 0x01f2, 0x004b, +0x02de, 0x0367, 0x01ac, 0x0015, +0x0183, 0x003a, 0x02f1, 0x0348, +0x0262, 0x03db, 0x0110, 0x00a9, +0x013f, 0x0086, 0x024d, 0x03f4, +0x0161, 0x00d8, 0x0213, 0x03aa, +0x023c, 0x0385, 0x014e, 0x00f7, +0x031a, 0x02a3, 0x0068, 0x01d1, +0x0047, 0x01fe, 0x0335, 0x028c, +0x0019, 0x01a0, 0x036b, 0x02d2, +0x0344, 0x02fd, 0x0036, 0x018f, +0x00a5, 0x011c, 0x03d7, 0x026e, +0x03f8, 0x0241, 0x008a, 0x0133, +0x03a6, 0x021f, 0x00d4, 0x016d, +0x00fb, 0x0142, 0x0389, 0x0230, +0x0184, 0x003d, 0x02f6, 0x034f, +0x02d9, 0x0360, 0x01ab, 0x0012, +0x0287, 0x033e, 0x01f5, 0x004c, +0x01da, 0x0063, 0x02a8, 0x0311, +0x023b, 0x0382, 0x0149, 0x00f0, +0x0166, 0x00df, 0x0214, 0x03ad, +0x0138, 0x0081, 0x024a, 0x03f3, +0x0265, 0x03dc, 0x0117, 0x00ae, +0x0343, 0x02fa, 0x0031, 0x0188, +0x001e, 0x01a7, 0x036c, 0x02d5, +0x0040, 0x01f9, 0x0332, 0x028b, +0x031d, 0x02a4, 0x006f, 0x01d6, +0x00fc, 0x0145, 0x038e, 0x0237, +0x03a1, 0x0218, 0x00d3, 0x016a, +0x03ff, 0x0246, 0x008d, 0x0134, +0x00a2, 0x011b, 0x03d0, 0x0269, +0x01b3, 0x000a, 0x02c1, 0x0378, +0x02ee, 0x0357, 0x019c, 0x0025, +0x02b0, 0x0309, 0x01c2, 0x007b, +0x01ed, 0x0054, 0x029f, 0x0326, +0x020c, 0x03b5, 0x017e, 0x00c7, +0x0151, 0x00e8, 0x0223, 0x039a, +0x010f, 0x00b6, 0x027d, 0x03c4, +0x0252, 0x03eb, 0x0120, 0x0099, +0x0374, 0x02cd, 0x0006, 0x01bf, +0x0029, 0x0190, 0x035b, 0x02e2, +0x0077, 0x01ce, 0x0305, 0x02bc, +0x032a, 0x0293, 0x0058, 0x01e1, +0x00cb, 0x0172, 0x03b9, 0x0200, +0x0396, 0x022f, 0x00e4, 0x015d, +0x03c8, 0x0271, 0x00ba, 0x0103, +0x0095, 0x012c, 0x03e7, 0x025e, +0x0136, 0x008f, 0x0244, 0x03fd, +0x026b, 0x03d2, 0x0119, 0x00a0, +0x0235, 0x038c, 0x0147, 0x00fe, +0x0168, 0x00d1, 0x021a, 0x03a3, +0x0289, 0x0330, 0x01fb, 0x0042, +0x01d4, 0x006d, 0x02a6, 0x031f, +0x018a, 0x0033, 0x02f8, 0x0341, +0x02d7, 0x036e, 0x01a5, 0x001c, +0x03f1, 0x0248, 0x0083, 0x013a, +0x00ac, 0x0115, 0x03de, 0x0267, +0x00f2, 0x014b, 0x0380, 0x0239, +0x03af, 0x0216, 0x00dd, 0x0164, +0x004e, 0x01f7, 0x033c, 0x0285, +0x0313, 0x02aa, 0x0061, 0x01d8, +0x034d, 0x02f4, 0x003f, 0x0186, +0x0010, 0x01a9, 0x0362, 0x02db, +0x0101, 0x00b8, 0x0273, 0x03ca, +0x025c, 0x03e5, 0x012e, 0x0097, +0x0202, 0x03bb, 0x0170, 0x00c9, +0x015f, 0x00e6, 0x022d, 0x0394, +0x02be, 0x0307, 0x01cc, 0x0075, +0x01e3, 0x005a, 0x0291, 0x0328, +0x01bd, 0x0004, 0x02cf, 0x0376, +0x02e0, 0x0359, 0x0192, 0x002b, +0x03c6, 0x027f, 0x00b4, 0x010d, +0x009b, 0x0122, 0x03e9, 0x0250, +0x00c5, 0x017c, 0x03b7, 0x020e, +0x0398, 0x0221, 0x00ea, 0x0153, +0x0079, 0x01c0, 0x030b, 0x02b2, +0x0324, 0x029d, 0x0056, 0x01ef, +0x037a, 0x02c3, 0x0008, 0x01b1, +0x0027, 0x019e, 0x0355, 0x02ec, +0x0158, 0x00e1, 0x022a, 0x0393, +0x0205, 0x03bc, 0x0177, 0x00ce, +0x025b, 0x03e2, 0x0129, 0x0090, +0x0106, 0x00bf, 0x0274, 0x03cd, +0x02e7, 0x035e, 0x0195, 0x002c, +0x01ba, 0x0003, 0x02c8, 0x0371, +0x01e4, 0x005d, 0x0296, 0x032f, +0x02b9, 0x0300, 0x01cb, 0x0072, +0x039f, 0x0226, 0x00ed, 0x0154, +0x00c2, 0x017b, 0x03b0, 0x0209, +0x009c, 0x0125, 0x03ee, 0x0257, +0x03c1, 0x0278, 0x00b3, 0x010a, +0x0020, 0x0199, 0x0352, 0x02eb, +0x037d, 0x02c4, 0x000f, 0x01b6, +0x0323, 0x029a, 0x0051, 0x01e8, +0x007e, 0x01c7, 0x030c, 0x02b5, +0x016f, 0x00d6, 0x021d, 0x03a4, +0x0232, 0x038b, 0x0140, 0x00f9, +0x026c, 0x03d5, 0x011e, 0x00a7, +0x0131, 0x0088, 0x0243, 0x03fa, +0x02d0, 0x0369, 0x01a2, 0x001b, +0x018d, 0x0034, 0x02ff, 0x0346, +0x01d3, 0x006a, 0x02a1, 0x0318, +0x028e, 0x0337, 0x01fc, 0x0045, +0x03a8, 0x0211, 0x00da, 0x0163, +0x00f5, 0x014c, 0x0387, 0x023e, +0x00ab, 0x0112, 0x03d9, 0x0260, +0x03f6, 0x024f, 0x0084, 0x013d, +0x0017, 0x01ae, 0x0365, 0x02dc, +0x034a, 0x02f3, 0x0038, 0x0181, +0x0314, 0x02ad, 0x0066, 0x01df, +0x0049, 0x01f0, 0x033b, 0x0282, +0x0322, 0x029b, 0x0050, 0x01e9, +0x007f, 0x01c6, 0x030d, 0x02b4, +0x0021, 0x0198, 0x0353, 0x02ea, +0x037c, 0x02c5, 0x000e, 0x01b7, +0x009d, 0x0124, 0x03ef, 0x0256, +0x03c0, 0x0279, 0x00b2, 0x010b, +0x039e, 0x0227, 0x00ec, 0x0155, +0x00c3, 0x017a, 0x03b1, 0x0208, +0x01e5, 0x005c, 0x0297, 0x032e, +0x02b8, 0x0301, 0x01ca, 0x0073, +0x02e6, 0x035f, 0x0194, 0x002d, +0x01bb, 0x0002, 0x02c9, 0x0370, +0x025a, 0x03e3, 0x0128, 0x0091, +0x0107, 0x00be, 0x0275, 0x03cc, +0x0159, 0x00e0, 0x022b, 0x0392, +0x0204, 0x03bd, 0x0176, 0x00cf, +0x0315, 0x02ac, 0x0067, 0x01de, +0x0048, 0x01f1, 0x033a, 0x0283, +0x0016, 0x01af, 0x0364, 0x02dd, +0x034b, 0x02f2, 0x0039, 0x0180, +0x00aa, 0x0113, 0x03d8, 0x0261, +0x03f7, 0x024e, 0x0085, 0x013c, +0x03a9, 0x0210, 0x00db, 0x0162, +0x00f4, 0x014d, 0x0386, 0x023f, +0x01d2, 0x006b, 0x02a0, 0x0319, +0x028f, 0x0336, 0x01fd, 0x0044, +0x02d1, 0x0368, 0x01a3, 0x001a, +0x018c, 0x0035, 0x02fe, 0x0347, +0x026d, 0x03d4, 0x011f, 0x00a6, +0x0130, 0x0089, 0x0242, 0x03fb, +0x016e, 0x00d7, 0x021c, 0x03a5, +0x0233, 0x038a, 0x0141, 0x00f8, +0x034c, 0x02f5, 0x003e, 0x0187, +0x0011, 0x01a8, 0x0363, 0x02da, +0x004f, 0x01f6, 0x033d, 0x0284, +0x0312, 0x02ab, 0x0060, 0x01d9, +0x00f3, 0x014a, 0x0381, 0x0238, +0x03ae, 0x0217, 0x00dc, 0x0165, +0x03f0, 0x0249, 0x0082, 0x013b, +0x00ad, 0x0114, 0x03df, 0x0266, +0x018b, 0x0032, 0x02f9, 0x0340, +0x02d6, 0x036f, 0x01a4, 0x001d, +0x0288, 0x0331, 0x01fa, 0x0043, +0x01d5, 0x006c, 0x02a7, 0x031e, +0x0234, 0x038d, 0x0146, 0x00ff, +0x0169, 0x00d0, 0x021b, 0x03a2, +0x0137, 0x008e, 0x0245, 0x03fc, +0x026a, 0x03d3, 0x0118, 0x00a1, +0x037b, 0x02c2, 0x0009, 0x01b0, +0x0026, 0x019f, 0x0354, 0x02ed, +0x0078, 0x01c1, 0x030a, 0x02b3, +0x0325, 0x029c, 0x0057, 0x01ee, +0x00c4, 0x017d, 0x03b6, 0x020f, +0x0399, 0x0220, 0x00eb, 0x0152, +0x03c7, 0x027e, 0x00b5, 0x010c, +0x009a, 0x0123, 0x03e8, 0x0251, +0x01bc, 0x0005, 0x02ce, 0x0377, +0x02e1, 0x0358, 0x0193, 0x002a, +0x02bf, 0x0306, 0x01cd, 0x0074, +0x01e2, 0x005b, 0x0290, 0x0329, +0x0203, 0x03ba, 0x0171, 0x00c8, +0x015e, 0x00e7, 0x022c, 0x0395, +0x0100, 0x00b9, 0x0272, 0x03cb, +0x025d, 0x03e4, 0x012f, 0x0096, +0x03fe, 0x0247, 0x008c, 0x0135, +0x00a3, 0x011a, 0x03d1, 0x0268, +0x00fd, 0x0144, 0x038f, 0x0236, +0x03a0, 0x0219, 0x00d2, 0x016b, +0x0041, 0x01f8, 0x0333, 0x028a, +0x031c, 0x02a5, 0x006e, 0x01d7, +0x0342, 0x02fb, 0x0030, 0x0189, +0x001f, 0x01a6, 0x036d, 0x02d4, +0x0139, 0x0080, 0x024b, 0x03f2, +0x0264, 0x03dd, 0x0116, 0x00af, +0x023a, 0x0383, 0x0148, 0x00f1, +0x0167, 0x00de, 0x0215, 0x03ac, +0x0286, 0x033f, 0x01f4, 0x004d, +0x01db, 0x0062, 0x02a9, 0x0310, +0x0185, 0x003c, 0x02f7, 0x034e, +0x02d8, 0x0361, 0x01aa, 0x0013, +0x03c9, 0x0270, 0x00bb, 0x0102, +0x0094, 0x012d, 0x03e6, 0x025f, +0x00ca, 0x0173, 0x03b8, 0x0201, +0x0397, 0x022e, 0x00e5, 0x015c, +0x0076, 0x01cf, 0x0304, 0x02bd, +0x032b, 0x0292, 0x0059, 0x01e0, +0x0375, 0x02cc, 0x0007, 0x01be, +0x0028, 0x0191, 0x035a, 0x02e3, +0x010e, 0x00b7, 0x027c, 0x03c5, +0x0253, 0x03ea, 0x0121, 0x0098, +0x020d, 0x03b4, 0x017f, 0x00c6, +0x0150, 0x00e9, 0x0222, 0x039b, +0x02b1, 0x0308, 0x01c3, 0x007a, +0x01ec, 0x0055, 0x029e, 0x0327, +0x01b2, 0x000b, 0x02c0, 0x0379, +0x02ef, 0x0356, 0x019d, 0x0024, +0x0390, 0x0229, 0x00e2, 0x015b, +0x00cd, 0x0174, 0x03bf, 0x0206, +0x0093, 0x012a, 0x03e1, 0x0258, +0x03ce, 0x0277, 0x00bc, 0x0105, +0x002f, 0x0196, 0x035d, 0x02e4, +0x0372, 0x02cb, 0x0000, 0x01b9, +0x032c, 0x0295, 0x005e, 0x01e7, +0x0071, 0x01c8, 0x0303, 0x02ba, +0x0157, 0x00ee, 0x0225, 0x039c, +0x020a, 0x03b3, 0x0178, 0x00c1, +0x0254, 0x03ed, 0x0126, 0x009f, +0x0109, 0x00b0, 0x027b, 0x03c2, +0x02e8, 0x0351, 0x019a, 0x0023, +0x01b5, 0x000c, 0x02c7, 0x037e, +0x01eb, 0x0052, 0x0299, 0x0320, +0x02b6, 0x030f, 0x01c4, 0x007d, +0x03a7, 0x021e, 0x00d5, 0x016c, +0x00fa, 0x0143, 0x0388, 0x0231, +0x00a4, 0x011d, 0x03d6, 0x026f, +0x03f9, 0x0240, 0x008b, 0x0132, +0x0018, 0x01a1, 0x036a, 0x02d3, +0x0345, 0x02fc, 0x0037, 0x018e, +0x031b, 0x02a2, 0x0069, 0x01d0, +0x0046, 0x01ff, 0x0334, 0x028d, +0x0160, 0x00d9, 0x0212, 0x03ab, +0x023d, 0x0384, 0x014f, 0x00f6, +0x0263, 0x03da, 0x0111, 0x00a8, +0x013e, 0x0087, 0x024c, 0x03f5, +0x02df, 0x0366, 0x01ad, 0x0014, +0x0182, 0x003b, 0x02f0, 0x0349, +0x01dc, 0x0065, 0x02ae, 0x0317, +0x0281, 0x0338, 0x01f3, 0x004a, +0x029a, 0x0323, 0x01e8, 0x0051, +0x01c7, 0x007e, 0x02b5, 0x030c, +0x0199, 0x0020, 0x02eb, 0x0352, +0x02c4, 0x037d, 0x01b6, 0x000f, +0x0125, 0x009c, 0x0257, 0x03ee, +0x0278, 0x03c1, 0x010a, 0x00b3, +0x0226, 0x039f, 0x0154, 0x00ed, +0x017b, 0x00c2, 0x0209, 0x03b0, +0x005d, 0x01e4, 0x032f, 0x0296, +0x0300, 0x02b9, 0x0072, 0x01cb, +0x035e, 0x02e7, 0x002c, 0x0195, +0x0003, 0x01ba, 0x0371, 0x02c8, +0x03e2, 0x025b, 0x0090, 0x0129, +0x00bf, 0x0106, 0x03cd, 0x0274, +0x00e1, 0x0158, 0x0393, 0x022a, +0x03bc, 0x0205, 0x00ce, 0x0177, +0x02ad, 0x0314, 0x01df, 0x0066, +0x01f0, 0x0049, 0x0282, 0x033b, +0x01ae, 0x0017, 0x02dc, 0x0365, +0x02f3, 0x034a, 0x0181, 0x0038, +0x0112, 0x00ab, 0x0260, 0x03d9, +0x024f, 0x03f6, 0x013d, 0x0084, +0x0211, 0x03a8, 0x0163, 0x00da, +0x014c, 0x00f5, 0x023e, 0x0387, +0x006a, 0x01d3, 0x0318, 0x02a1, +0x0337, 0x028e, 0x0045, 0x01fc, +0x0369, 0x02d0, 0x001b, 0x01a2, +0x0034, 0x018d, 0x0346, 0x02ff, +0x03d5, 0x026c, 0x00a7, 0x011e, +0x0088, 0x0131, 0x03fa, 0x0243, +0x00d6, 0x016f, 0x03a4, 0x021d, +0x038b, 0x0232, 0x00f9, 0x0140, +0x02f4, 0x034d, 0x0186, 0x003f, +0x01a9, 0x0010, 0x02db, 0x0362, +0x01f7, 0x004e, 0x0285, 0x033c, +0x02aa, 0x0313, 0x01d8, 0x0061, +0x014b, 0x00f2, 0x0239, 0x0380, +0x0216, 0x03af, 0x0164, 0x00dd, +0x0248, 0x03f1, 0x013a, 0x0083, +0x0115, 0x00ac, 0x0267, 0x03de, +0x0033, 0x018a, 0x0341, 0x02f8, +0x036e, 0x02d7, 0x001c, 0x01a5, +0x0330, 0x0289, 0x0042, 0x01fb, +0x006d, 0x01d4, 0x031f, 0x02a6, +0x038c, 0x0235, 0x00fe, 0x0147, +0x00d1, 0x0168, 0x03a3, 0x021a, +0x008f, 0x0136, 0x03fd, 0x0244, +0x03d2, 0x026b, 0x00a0, 0x0119, +0x02c3, 0x037a, 0x01b1, 0x0008, +0x019e, 0x0027, 0x02ec, 0x0355, +0x01c0, 0x0079, 0x02b2, 0x030b, +0x029d, 0x0324, 0x01ef, 0x0056, +0x017c, 0x00c5, 0x020e, 0x03b7, +0x0221, 0x0398, 0x0153, 0x00ea, +0x027f, 0x03c6, 0x010d, 0x00b4, +0x0122, 0x009b, 0x0250, 0x03e9, +0x0004, 0x01bd, 0x0376, 0x02cf, +0x0359, 0x02e0, 0x002b, 0x0192, +0x0307, 0x02be, 0x0075, 0x01cc, +0x005a, 0x01e3, 0x0328, 0x0291, +0x03bb, 0x0202, 0x00c9, 0x0170, +0x00e6, 0x015f, 0x0394, 0x022d, +0x00b8, 0x0101, 0x03ca, 0x0273, +0x03e5, 0x025c, 0x0097, 0x012e, +0x0246, 0x03ff, 0x0134, 0x008d, +0x011b, 0x00a2, 0x0269, 0x03d0, +0x0145, 0x00fc, 0x0237, 0x038e, +0x0218, 0x03a1, 0x016a, 0x00d3, +0x01f9, 0x0040, 0x028b, 0x0332, +0x02a4, 0x031d, 0x01d6, 0x006f, +0x02fa, 0x0343, 0x0188, 0x0031, +0x01a7, 0x001e, 0x02d5, 0x036c, +0x0081, 0x0138, 0x03f3, 0x024a, +0x03dc, 0x0265, 0x00ae, 0x0117, +0x0382, 0x023b, 0x00f0, 0x0149, +0x00df, 0x0166, 0x03ad, 0x0214, +0x033e, 0x0287, 0x004c, 0x01f5, +0x0063, 0x01da, 0x0311, 0x02a8, +0x003d, 0x0184, 0x034f, 0x02f6, +0x0360, 0x02d9, 0x0012, 0x01ab, +0x0271, 0x03c8, 0x0103, 0x00ba, +0x012c, 0x0095, 0x025e, 0x03e7, +0x0172, 0x00cb, 0x0200, 0x03b9, +0x022f, 0x0396, 0x015d, 0x00e4, +0x01ce, 0x0077, 0x02bc, 0x0305, +0x0293, 0x032a, 0x01e1, 0x0058, +0x02cd, 0x0374, 0x01bf, 0x0006, +0x0190, 0x0029, 0x02e2, 0x035b, +0x00b6, 0x010f, 0x03c4, 0x027d, +0x03eb, 0x0252, 0x0099, 0x0120, +0x03b5, 0x020c, 0x00c7, 0x017e, +0x00e8, 0x0151, 0x039a, 0x0223, +0x0309, 0x02b0, 0x007b, 0x01c2, +0x0054, 0x01ed, 0x0326, 0x029f, +0x000a, 0x01b3, 0x0378, 0x02c1, +0x0357, 0x02ee, 0x0025, 0x019c, +0x0228, 0x0391, 0x015a, 0x00e3, +0x0175, 0x00cc, 0x0207, 0x03be, +0x012b, 0x0092, 0x0259, 0x03e0, +0x0276, 0x03cf, 0x0104, 0x00bd, +0x0197, 0x002e, 0x02e5, 0x035c, +0x02ca, 0x0373, 0x01b8, 0x0001, +0x0294, 0x032d, 0x01e6, 0x005f, +0x01c9, 0x0070, 0x02bb, 0x0302, +0x00ef, 0x0156, 0x039d, 0x0224, +0x03b2, 0x020b, 0x00c0, 0x0179, +0x03ec, 0x0255, 0x009e, 0x0127, +0x00b1, 0x0108, 0x03c3, 0x027a, +0x0350, 0x02e9, 0x0022, 0x019b, +0x000d, 0x01b4, 0x037f, 0x02c6, +0x0053, 0x01ea, 0x0321, 0x0298, +0x030e, 0x02b7, 0x007c, 0x01c5, +0x021f, 0x03a6, 0x016d, 0x00d4, +0x0142, 0x00fb, 0x0230, 0x0389, +0x011c, 0x00a5, 0x026e, 0x03d7, +0x0241, 0x03f8, 0x0133, 0x008a, +0x01a0, 0x0019, 0x02d2, 0x036b, +0x02fd, 0x0344, 0x018f, 0x0036, +0x02a3, 0x031a, 0x01d1, 0x0068, +0x01fe, 0x0047, 0x028c, 0x0335, +0x00d8, 0x0161, 0x03aa, 0x0213, +0x0385, 0x023c, 0x00f7, 0x014e, +0x03db, 0x0262, 0x00a9, 0x0110, +0x0086, 0x013f, 0x03f4, 0x024d, +0x0367, 0x02de, 0x0015, 0x01ac, +0x003a, 0x0183, 0x0348, 0x02f1, +0x0064, 0x01dd, 0x0316, 0x02af, +0x0339, 0x0280, 0x004b, 0x01f2, +}; +#endif /*USE_RDS_HW_DECODER*/ + +#endif /*fm_rds.h*/ diff --git a/drivers/media/radio/s610/radio-s610.c b/drivers/media/radio/s610/radio-s610.c new file mode 100644 index 000000000000..07817a376e6b --- /dev/null +++ b/drivers/media/radio/s610/radio-s610.c @@ -0,0 +1,2609 @@ +/* + * drivers/media/radio/s610/radio-s610.c -- V4L2 driver for S610 chips + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SCSC_FM +#include +#endif + +#include "fm_low_struc.h" +#include "radio-s610.h" + +//#include "../../../../sound/soc/samsung/abox/abox.h" + +static int radio_region; +module_param(radio_region, int, 0); +MODULE_PARM_DESC(radio_region, "Region: 0=Europe/US, 1=Japan"); + +struct s610_radio; + +/* global variable for radio structure */ +struct s610_radio *gradio; + +#define FAC_VALUE 16000 + +static int s610_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); +static int s610_radio_s_ctrl(struct v4l2_ctrl *ctrl); +static int s610_core_set_power_state(struct s610_radio *radio, + u8 next_state); +int fm_set_mute_mode(struct s610_radio *radio, u8 mute_mode_toset); +static int fm_radio_runtime_resume(struct device *dev); +void enable_FM_mux_clk_aud(struct s610_radio *radio); +void disable_clk_gating(struct s610_radio *radio); + +signed int exynos_get_fm_open_status(void); +signed int shared_fm_open_cnt; + +extern fm_conf_ini_values low_fm_conf_init; +u32 *fm_spur_init; +u32 *fm_spur_trf_init; +u32 *fm_dual_clk_init; +u32 vol_level_init[FM_RX_VOLUME_GAIN_STEP] = { + 0, 16, 23, 32, 45, 64, 90, 128, 181, 256, 362, 512, 724, 1024, 1447, 2047 }; + +static const struct v4l2_ctrl_ops s610_ctrl_ops = { + .s_ctrl = s610_radio_s_ctrl, + .g_volatile_ctrl = s610_radio_g_volatile_ctrl, + +}; + +enum s610_ctrl_idx { + S610_IDX_CH_SPACING = 0x01, + S610_IDX_CH_BAND = 0x02, + S610_IDX_SOFT_STEREO_BLEND = 0x03, + S610_IDX_SOFT_STEREO_BLEND_COEFF = 0x04, + S610_IDX_SOFT_MUTE_COEFF = 0x05, + S610_IDX_RSSI_CURR = 0x06, + S610_IDX_SNR_CURR = 0x07, + S610_IDX_SEEK_CANCEL = 0x08, + S610_IDX_SEEK_MODE = 0x09, + S610_IDX_RDS_ON = 0x0A, + S610_IDX_IF_COUNT1 = 0x0B, + S610_IDX_IF_COUNT2 = 0x0C, + S610_IDX_RSSI_TH = 0x0D, + S610_IDX_KERNEL_VER = 0x0E, + S610_IDX_SOFT_STEREO_BLEND_REF = 0x0F +}; + +static struct v4l2_ctrl_config s610_ctrls[] = { + /** + * S610 during its station seeking(or tuning) process uses several + * parameters to detrmine if "the station" is valid: + * + * - Signal's RSSI(in dBuV) must be greater than + * #V4L2_CID_S610_RSSI_THRESHOLD + */ + [S610_IDX_CH_SPACING] = {/*0x01*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_CH_SPACING, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Channel Spacing", + .min = 0, + .max = 2, + .step = 1, + }, + [S610_IDX_CH_BAND] = { /*0x02*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_CH_BAND, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Channel Band", + .min = 0, + .max = 1, + .step = 1, + }, + [S610_IDX_SOFT_STEREO_BLEND] = { /*0x03*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SOFT_STEREO_BLEND, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Soft Stereo Blend", + .min = 0, + .max = 1, + .step = 1, + }, + [S610_IDX_SOFT_STEREO_BLEND_COEFF] = { /*0x04*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Soft Stereo Blend COEFF", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_SOFT_MUTE_COEFF] = { /*0x05*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SOFT_MUTE_COEFF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Soft Mute COEFF Set", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_RSSI_CURR] = { /*0x06*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_RSSI_CURR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RSSI Current", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_SNR_CURR] = { /*0x07*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SNR_CURR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "SNR Current", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_SEEK_CANCEL] = { /*0x08*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SEEK_CANCEL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Seek Cancel", + .min = 0, + .max = 1, + .step = 1, + }, + [S610_IDX_SEEK_MODE] = { /*0x09*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SEEK_MODE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Seek Mode", + .min = 0, + .max = 4, + .step = 1, + }, + [S610_IDX_RDS_ON] = { /*0x0A*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_RDS_ON, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS ON", + .min = 0, + .max = 0x0F, + .step = 1, + }, + [S610_IDX_IF_COUNT1] = { /*0x0B*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_IF_COUNT1, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "IF_COUNT1", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_IF_COUNT2] = { /*0x0C*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_IF_COUNT2, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "IF_COUNT2", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_RSSI_TH] = { /*0x0D*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_RSSI_TH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RSSI Th", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_KERNEL_VER] = { /*0x0E*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_KERNEL_VER, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "KERNEL_VER", + .min = 0, + .max = 0xffff, + .step = 1, + }, + [S610_IDX_SOFT_STEREO_BLEND_REF] = { /*0x0F*/ + .ops = &s610_ctrl_ops, + .id = V4L2_CID_S610_SOFT_STEREO_BLEND_REF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Soft Stereo Blend Ref", + .min = 0, + .max = 0xffff, + .step = 1, + }, +}; + +static const struct v4l2_frequency_band s610_bands[] = { + [0] = { + .type = V4L2_TUNER_RADIO, + .index = S610_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + /* default region Eu/US */ + .rangelow = 87500*FAC_VALUE, + .rangehigh = 108000*FAC_VALUE, + .modulation = V4L2_BAND_MODULATION_FM, + }, + [1] = { + .type = V4L2_TUNER_RADIO, + .index = S610_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + /* default region Eu/US */ + .rangelow = 76000*FAC_VALUE, + .rangehigh = 90000*FAC_VALUE, + .modulation = V4L2_BAND_MODULATION_FM, + }, +}; + +/* Region info */ +static struct region_info region_configs[] = { + /* Europe/US */ + { + .chanl_space = FM_CHANNEL_SPACING_200KHZ * FM_FREQ_MUL, + .bot_freq = 87500, /* 87.5 MHz */ + .top_freq = 108000, /* 108 MHz */ + .fm_band = 0, + }, + /* Japan */ + { + .chanl_space = FM_CHANNEL_SPACING_200KHZ * FM_FREQ_MUL, + .bot_freq = 76000, /* 76 MHz */ + .top_freq = 90000, /* 90 MHz */ + .fm_band = 1, + }, +}; + +static inline bool s610_radio_freq_is_inside_of_the_band(u32 freq, int band) +{ + return freq >= region_configs[radio_region].bot_freq && + freq <= region_configs[radio_region].top_freq; +} + +static inline struct s610_radio * +v4l2_dev_to_radio(struct v4l2_device *d) +{ + return container_of(d, struct s610_radio, v4l2dev); +} + +static inline struct s610_radio * +v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) +{ + return container_of(d, struct s610_radio, ctrl_handler); +} + +/* + * s610_vidioc_querycap - query device capabilities + */ +static int s610_radio_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct s610_radio *radio = video_drvdata(file); + + FUNC_ENTRY(radio); + + s610_core_lock(radio->core); + + strlcpy(capability->driver, radio->v4l2dev.name, + sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", radio->v4l2dev.name); + + capability->device_caps = V4L2_CAP_TUNER + | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE + | V4L2_CAP_READWRITE | V4L2_CAP_HW_FREQ_SEEK; + + capability->capabilities = capability->device_caps + | V4L2_CAP_DEVICE_CAPS; + + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return 0; +} + +static int s610_radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + int ret; + struct s610_radio *radio = video_drvdata(file); + + FUNC_ENTRY(radio); + + if (band->tuner != 0) + return -EINVAL; + + s610_core_lock(radio->core); + + if (band->index == S610_BAND_FM) { + *band = s610_bands[radio_region]; + ret = 0; + } else { + ret = -EINVAL; + } + + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int ret; + struct s610_radio *radio = video_drvdata(file); + u16 payload; + + FUNC_ENTRY(radio); + + if (tuner->index != 0) + return -EINVAL; + + s610_core_lock(radio->core); + + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_HWSEEK_BOUNDED + | V4L2_TUNER_CAP_HWSEEK_WRAP + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + + + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + + /* Read register : MONO, Stereo mode */ + /*ret = low_get_most_mode(radio, &payload);*/ + payload = radio->low->fm_state.force_mono ? + 0 : MODE_MASK_MONO_STEREO; + radio->audmode = payload; + tuner->audmode = radio->audmode; + tuner->afc = 1; + tuner->rangelow = s610_bands[radio_region].rangelow; + tuner->rangehigh = s610_bands[radio_region].rangehigh; + + ret = low_get_search_lvl(radio, &payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to read reg for SEARCH_LVL\n"); + ret = -EIO; + tuner->signal = 0; + } else { + tuner->signal = 0; + if (payload & 0x80) + tuner->signal = 0xFF00; + else + tuner->signal = 0; + + tuner->signal |= (payload & 0xFF); + } + + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + int ret; + struct s610_radio *radio = video_drvdata(file); + u16 payload; + + FUNC_ENTRY(radio); + + if (tuner->index != 0) + return -EINVAL; + + s610_core_lock(radio->core); + + if (tuner->audmode == V4L2_TUNER_MODE_MONO || + tuner->audmode == V4L2_TUNER_MODE_STEREO) + radio->audmode = tuner->audmode; + else + radio->audmode = V4L2_TUNER_MODE_STEREO; + + payload = radio->audmode; + ret = low_set_most_mode(radio, payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for MOST MODE clear\n"); + ret = -EIO; + } + + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_pretune(struct s610_radio *radio) +{ + int ret = 0; + u16 payload; + + FUNC_ENTRY(radio); + + /*ret = low_get_flag(radio, &payload);*/ + payload = fm_get_flags(radio); + + payload = 0; + ret = low_set_mute_state(radio, payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for MUTE state clean\n"); + return -EIO; + } + + payload = radio->low->fm_state.force_mono ? + 0 : MODE_MASK_MONO_STEREO; + + payload = 0; + ret = low_set_most_blend(radio, payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for MOST blend clean\n"); + return -EIO; + } + + payload = 0; + fm_set_band(radio, payload); + + payload = 0; + ret = low_set_demph_mode(radio, payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for FM_SUBADDR_DEMPH_MODE clean\n"); + return -EIO; + } + + payload = 0; + radio->low->fm_state.use_rbds = + ((payload & RDS_SYSTEM_MASK_RDS) != 0); + radio->low->fm_state.save_eblks = + ((payload & RDS_SYSTEM_MASK_EBLK) == 0); + + radio->low->fm_state.rds_mem_thresh = RDS_MEM_MAX_THRESH; + + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct s610_radio *radio = video_drvdata(file); + u32 payload; + + FUNC_ENTRY(radio); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + s610_core_lock(radio->core); + + payload = radio->low->fm_state.freq; + f->frequency = payload; + f->frequency *= FAC_VALUE; + + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + FDEBUG(radio, + "%s():freq.tuner:%d, type:%d, freq:%d, real-freq:%d\n", + __func__, f->tuner, f->type, f->frequency, + f->frequency/FAC_VALUE); + + return 0; +} + +int fm_set_frequency(struct s610_radio *radio, u32 freq) +{ + unsigned long timeleft; + u32 curr_frq, intr_flag; + u32 curr_frq_in_khz; + int ret; + u32 payload; + u16 payload16; + + FUNC_ENTRY(radio); + + /* Calculate frequency with offset and set*/ + payload = real_to_api(freq); + + low_set_freq(radio, payload); + + /* Read flags - just to clear any pending interrupts if we had */ + payload = fm_get_flags(radio); + + /* Enable FR, BL interrupts */ + intr_flag = radio->irq_mask; + radio->irq_mask = (FM_EVENT_TUNED | FM_EVENT_BD_LMT); + + low_get_search_lvl(radio, (u16 *) &payload16); + if (!payload16) { + payload16 = FM_DEFAULT_RSSI_THRESHOLD; + low_set_search_lvl(radio, (u16) payload16); + } + + if (radio->low->fm_config.search_conf.normal_ifca_m == 0) + radio->low->fm_config.search_conf.normal_ifca_m = + low_fm_conf_init.search_conf.normal_ifca_m; + + if (radio->low->fm_config.search_conf.weak_ifca_m == 0) + radio->low->fm_config.search_conf.weak_ifca_m = + low_fm_conf_init.search_conf.weak_ifca_m; + + FDEBUG(radio, "%s(): ifcount:W-%d N-%d\n", __func__, + radio->low->fm_config.search_conf.weak_ifca_m, + radio->low->fm_config.search_conf.normal_ifca_m); + + if (!radio->low->fm_config.mute_coeffs_soft) { + radio->low->fm_config.mute_coeffs_soft = + low_fm_conf_init.mute_coeffs_soft; + } + + if (!radio->low->fm_config.blend_coeffs_soft) { + radio->low->fm_config.blend_coeffs_soft = + low_fm_conf_init.blend_coeffs_soft; + } + + if (!radio->low->fm_config.blend_coeffs_switch) { + radio->low->fm_config.blend_coeffs_switch = + low_fm_conf_init.blend_coeffs_switch; + } + + if (!radio->low->fm_config.blend_coeffs_dis) + radio->low->fm_config.blend_coeffs_dis = + low_fm_conf_init.blend_coeffs_dis; + + fm_set_blend_mute(radio); + + init_completion(&radio->flags_set_fr_comp); + + /* Start tune */ + payload = FM_TUNER_PRESET_MODE; + ret = low_set_tuner_mode(radio, payload); + if (ret != 0) { + ret = -EIO; + goto exit; + } + + timeleft = jiffies_to_msecs(FM_DRV_TURN_TIMEOUT); + /* Wait for tune ended interrupt */ + timeleft = wait_for_completion_timeout(&radio->flags_set_fr_comp, + FM_DRV_TURN_TIMEOUT); + + if (!timeleft) { + dev_err(radio->v4l2dev.dev, + "Timeout(%d sec),didn't get tune ended int\n", + jiffies_to_msecs(FM_DRV_TURN_TIMEOUT) / 1000); + ret = -ETIMEDOUT; + goto exit; + } + + /* Read freq back to confirm */ + curr_frq = radio->low->fm_state.freq; + curr_frq_in_khz = curr_frq; + if (curr_frq_in_khz != freq) { + dev_err(radio->v4l2dev.dev, + "Set Freq (%d) but requested freq (%d)\n", + curr_frq_in_khz, freq); + ret = -ENODATA; + goto exit; + } + + FDEBUG(radio, + "%s():--> Set frequency: %d Read frequency:%d\n", + __func__, freq, curr_frq_in_khz); + + /* Update local cache */ + radio->freq = curr_frq_in_khz; +exit: + /* Re-enable default FM interrupts */ + radio->irq_mask = intr_flag; + + FDEBUG(radio, "wait_atomic: %d\n", radio->wait_atomic); + + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + int ret; + u32 freq = f->frequency; + struct s610_radio *radio = video_drvdata(file); + + FUNC_ENTRY(radio); + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (!wake_lock_active(&radio->wakelock)) + wake_lock(&radio->wakelock); + + s610_core_lock(radio->core); + FDEBUG(radio, "%s():freq:%d, real-freq:%d\n", + __func__, f->frequency, f->frequency/FAC_VALUE); + + freq /= FAC_VALUE; + + freq = clamp(freq, + region_configs[radio_region].bot_freq, + region_configs[radio_region].top_freq); + if (!s610_radio_freq_is_inside_of_the_band(freq, + radio_region)) { + ret = -EINVAL; + goto unlock; + } + + ret = fm_set_frequency(radio, freq); + +unlock: + s610_core_unlock(radio->core); + + if (wake_lock_active(&radio->wakelock)) + wake_unlock(&radio->wakelock); + + FUNC_EXIT(radio); + + FDEBUG(radio, + "%s():v4l2_frequency.tuner:%d,type:%d,frequency:%d\n", + __func__, f->tuner, f->type, freq); + + return ret; +} + +int fm_rx_seek(struct s610_radio *radio, u32 seek_upward, + u32 wrap_around, u32 spacing, u32 freq_low, u32 freq_hi) +{ + u32 curr_frq, save_freq; + u32 payload, int_reason, intr_flag, tune_mode; + u32 space, upward; + u16 payload16; + unsigned long timeleft; + int ret; + int bl_1st, bl_2nd, bl_3nd; + + FUNC_ENTRY(radio); + + radio->seek_freq = 0; + radio->wrap_around = 0; + bl_1st = 0; + bl_2nd = 0; + bl_3nd = 0; + + payload = fm_get_flags(radio); + + /* Set channel spacing */ + if (spacing > 0 && spacing <= 50) + payload = 0; /*CHANNEL_SPACING_50KHZ*/ + else if (spacing > 50 && spacing <= 100) + payload = 1; /*CHANNEL_SPACING_100KHZ*/ + else + payload = 2; /*CHANNEL_SPACING_200KHZ;*/ + + FDEBUG(radio, "%s(): init: spacing: %d\n", __func__, payload); + + radio->low->fm_tuner_state.freq_step = + radio->low->fm_freq_steps[payload]; + radio->region.chanl_space = 50 * (1 << payload); + + /* Check the offset in order to be aligned to the channel spacing*/ + space = radio->region.chanl_space; + + /* Set search direction (0:Seek Up, 1:Seek Down) */ + payload = (seek_upward ? FM_SEARCH_DIRECTION_UP : + FM_SEARCH_DIRECTION_DOWN); + upward = payload; + radio->low->fm_state.search_down = + !!(payload & SEARCH_DIR_MASK); + FDEBUG(radio, "%s(): direction: %d\n", __func__, payload); + + save_freq = freq_low; + radio->seek_freq = save_freq; + tune_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT; + + if (radio->low->fm_state.rssi_limit_search == 0) + low_set_search_lvl(radio, (u16)FM_DEFAULT_RSSI_THRESHOLD); + + if (radio->low->fm_config.search_conf.normal_ifca_m == 0) + radio->low->fm_config.search_conf.normal_ifca_m = + low_fm_conf_init.search_conf.normal_ifca_m; + + if (radio->low->fm_config.search_conf.weak_ifca_m == 0) + radio->low->fm_config.search_conf.weak_ifca_m = + low_fm_conf_init.search_conf.weak_ifca_m; + + FDEBUG(radio, "%s(): ifcount:W-%d N-%d\n", __func__, + radio->low->fm_config.search_conf.weak_ifca_m, + radio->low->fm_config.search_conf.normal_ifca_m); + +again: + curr_frq = freq_low; + if ((freq_low == region_configs[radio_region].bot_freq) + && (upward == FM_SEARCH_DIRECTION_DOWN)) + curr_frq = region_configs[radio_region].top_freq; + + if ((freq_low == region_configs[radio_region].top_freq) + && (upward == FM_SEARCH_DIRECTION_UP)) + curr_frq = region_configs[radio_region].bot_freq; + + payload = curr_frq; + low_set_freq(radio, payload); + + FDEBUG(radio, "%s(): curr_freq: %d, freq hi: %d\n", + __func__, curr_frq, freq_hi); + + /* Enable FR, BL interrupts */ + intr_flag = radio->irq_mask; + radio->irq_mask = (FM_EVENT_TUNED | FM_EVENT_BD_LMT); + low_get_search_lvl(radio, (u16 *) &payload16); + if (!payload16) { + payload16 = FM_DEFAULT_RSSI_THRESHOLD; + low_set_search_lvl(radio, (u16) payload16); + } + FDEBUG(radio, "%s(): SEARCH_LVL1: 0x%x\n", __func__, payload16); + + if (!radio->low->fm_config.mute_coeffs_soft) { + radio->low->fm_config.mute_coeffs_soft = + low_fm_conf_init.mute_coeffs_soft; + } + + if (!radio->low->fm_config.blend_coeffs_soft) { + radio->low->fm_config.blend_coeffs_soft = + low_fm_conf_init.blend_coeffs_soft; + } + + if (!radio->low->fm_config.blend_coeffs_switch) { + radio->low->fm_config.blend_coeffs_switch = + low_fm_conf_init.blend_coeffs_switch; + } + + if (!radio->low->fm_config.blend_coeffs_dis) + radio->low->fm_config.blend_coeffs_dis = + low_fm_conf_init.blend_coeffs_dis; + + fm_set_blend_mute(radio); + + reinit_completion(&radio->flags_seek_fr_comp); + + payload = tune_mode; + FDEBUG(radio, + "%s(): turn start mode: 0x%x\n", __func__, payload); + ret = low_set_tuner_mode(radio, (u16) payload); + if (ret != 0) + return -EIO; + + /* Wait for tune ended interrupt */ + timeleft = + wait_for_completion_timeout(&radio->flags_seek_fr_comp, + FM_DRV_SEEK_TIMEOUT); + + /* seek cancel status */ + if (radio->seek_status == FM_TUNER_STOP_SEARCH_MODE) { + dev_info(radio->dev, ">>> rev seek cancel"); + return -ENODATA; + } + + FDEBUG(radio, + "FDEBUG > Seek done rev complete!! freq %d, irq_flag: 0x%x, bl:%d\n", + radio->low->fm_state.freq, radio->irq_flag, bl_1st); + + int_reason = radio->low->fm_state.flags & + (FM_EVENT_TUNED | FM_EVENT_BD_LMT); + + if ((save_freq == region_configs[radio_region].bot_freq) + || (save_freq == region_configs[radio_region].top_freq)) + bl_1st = 1; + + if ((save_freq == region_configs[radio_region].bot_freq) + && (upward == FM_SEARCH_DIRECTION_UP)) { + bl_1st = 0; + bl_2nd = 1; + tune_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE; + } + if ((save_freq == region_configs[radio_region].top_freq) + && (upward == FM_SEARCH_DIRECTION_DOWN)) { + bl_1st = 0; + bl_2nd = 1; + tune_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE; + } + + if ((int_reason & FM_EVENT_BD_LMT) && (bl_1st == 0) && (bl_3nd == 0)) { + if (wrap_around) { + freq_low = radio->low->fm_state.freq; + bl_1st = 1; + if (bl_2nd) + bl_3nd = 1; + + /* Re-enable default FM interrupts */ + radio->irq_mask = intr_flag; + radio->wrap_around = 1; + FDEBUG(radio, "> bl set %d, %d, save %d\n", + radio->seek_freq, radio->wrap_around, save_freq); + + goto again; + } else + ret = -EINVAL; + } + + /* Read freq to know where operation tune operation stopped */ + payload = radio->low->fm_state.freq; + radio->freq = payload; + + dev_info(radio->v4l2dev.dev, "Seek freq %d\n", radio->freq); + + radio->seek_freq = 0; + radio->wrap_around = 0; + + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + int ret = 0; + struct s610_radio *radio = video_drvdata(file); + u32 seek_low, seek_hi, seek_spacing; + + FUNC_ENTRY(radio); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (seek->tuner != 0 || + seek->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (seek->rangelow >= seek->rangehigh) + ret = -EINVAL; + + seek_low = radio->low->fm_state.freq; + if (seek->seek_upward) + seek_hi = region_configs[radio_region].top_freq; + else + seek_hi = region_configs[radio_region].bot_freq; + + seek_spacing = seek->spacing / 1000; + FDEBUG(radio, "%s(): get freq low: %d, freq hi: %d\n", + __func__, seek_low, seek_hi); + FDEBUG(radio, + "%s(): upward:%d, warp: %d, spacing: %d\n", __func__, + seek->seek_upward, seek->wrap_around, seek->spacing); + + if (!wake_lock_active(&radio->wakelock)) + wake_lock(&radio->wakelock); + + ret = fm_rx_seek(radio, seek->seek_upward, seek->wrap_around, + seek_spacing, seek_low, seek_hi); + if (ret < 0) + dev_err(radio->v4l2dev.dev, "RX seek failed - %d\n", ret); + + if (wake_lock_active(&radio->wakelock)) + wake_unlock(&radio->wakelock); + + FUNC_EXIT(radio); + + return ret; +} + +/* Configures mute mode (Mute Off/On/Attenuate) */ +int fm_set_mute_mode(struct s610_radio *radio, u8 mute_mode_toset) +{ + u16 payload = radio->mute_mode; + int ret = 0; + + FUNC_ENTRY(radio); + + radio->mute_mode = mute_mode_toset; + + switch (radio->mute_mode) { + case FM_MUTE_ON: + payload = FM_RX_AC_MUTE_MODE; + break; + + case FM_MUTE_OFF: + payload = FM_RX_UNMUTE_MODE; + break; + } + + /* Write register : mute */ + ret = low_set_mute_state(radio, payload); + if (ret != 0) + ret = -EIO; + + FUNC_EXIT(radio); + + return ret; +} + +/* Enable/Disable RDS */ +int fm_set_rds_mode(struct s610_radio *radio, u8 rds_en_dis) +{ + int ret; + u16 payload; + int ii; + u32 fifo_tmp; + + FUNC_ENTRY(radio); + + if (rds_en_dis != FM_RDS_ENABLE && rds_en_dis != FM_RDS_DISABLE) { + dev_err(radio->v4l2dev.dev, "Invalid rds option\n"); + return -EINVAL; + } + + if (rds_en_dis == FM_RDS_ENABLE) { + if (!wake_lock_active(&radio->rdswakelock)) + wake_lock(&radio->rdswakelock); + mdelay(100); + atomic_set(&radio->is_rds_new, 0); + radio->rds_cnt_mod = 0; + radio->rds_n_count = 0; + radio->rds_r_count = 0; + radio->rds_read_cnt = 0; + radio->rds_sync_loss_cnt = 0; + + init_waitqueue_head(&radio->core->rds_read_queue); + + payload = radio->core->power_mode; + payload |= S610_POWER_ON_RDS; + ret = s610_core_set_power_state(radio, + payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to set for RDS power state\n"); + return -EIO; + } + + /* Write register : RDS on */ + /* Turn on RDS and RDS circuit */ + fm_rds_on(radio); + + /* Write register : clear RDS cache */ + /* flush RDS buffer */ + payload = FM_RX_RDS_FLUSH_FIFO; + ret = low_set_rds_cntr(radio, payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for default RDS_CNTR\n"); + return -EIO; + } + + /* Write register : clear panding interrupt flags */ + /* Read flags - just to clear any pending interrupts. */ + payload = fm_get_flags(radio); + + /* Write register : set RDS memory depth */ + /* Set RDS FIFO threshold value */ + if (radio->rds_parser_enable) + radio->low->fm_state.rds_mem_thresh = RDS_MEM_MAX_THRESH_PARSER; + else + radio->low->fm_state.rds_mem_thresh = RDS_MEM_MAX_THRESH; + + /* Write register : set RDS interrupt enable */ + /* Enable RDS interrupt */ + radio->irq_mask |= FM_EVENT_BUF_FUL; + + /* Update our local flag */ + radio->rds_flag = FM_RDS_ENABLE; + + /* RDS parser reset */ + if (radio->rds_parser_enable) + fm_rds_parser_reset(&(radio->pi)); + + /* FIFO clear */ + for (ii = 0; ii < 32; ii++) + fifo_tmp = fmspeedy_get_reg_work(0xFFF3C0); + +#ifdef RDS_POLLING_ENABLE + fm_rds_periodic_update((unsigned long) radio); +#endif /*RDS_POLLING_ENABLE*/ + } else if ( + rds_en_dis == FM_RDS_DISABLE) { + payload = radio->core->power_mode; + payload &= ~S610_POWER_ON_RDS; + ret = s610_core_set_power_state(radio, + payload); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to set for RDS power state\n"); + return -EIO; + } + + /* Write register : RDS off */ + /* Turn off RX RDS */ + fm_rds_off(radio); + + /* Update RDS local cache */ + radio->irq_mask &= ~(FM_EVENT_BUF_FUL); + radio->rds_flag = FM_RDS_DISABLE; +#ifdef RDS_POLLING_ENABLE + fm_rds_periodic_cancel((unsigned long) radio); +#endif /*RDS_POLLING_ENABLE*/ + + /* Service pending read */ + wake_up_interruptible(&radio->core->rds_read_queue); + + if (wake_lock_active(&radio->rdswakelock)) + wake_unlock(&radio->rdswakelock); + } + + FUNC_EXIT(radio); + + return ret; +} + +int fm_set_deemphasis(struct s610_radio *radio, u16 vol_to_set) +{ + int ret = 0; + u16 payload; + + payload = vol_to_set; + /* Write register : deemphasis */ + ret = low_set_demph_mode(radio, payload); + if (ret != 0) + return -EIO; + + radio->deemphasis_mode = vol_to_set; + + return ret; +} + +static int s610_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s610_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + u16 payload; + int ret = 0; + + FUNC_ENTRY(radio); + if (!radio) + return -ENODEV; + + s610_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_S610_CH_SPACING: + payload = radio->low->fm_tuner_state.freq_step / 100; + FDEBUG(radio, "%s(), FREQ_STEP val:%d, ret : %d\n", + __func__, payload, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_CH_BAND: + payload = radio->low->fm_state.band; + FDEBUG(radio, "%s(), BAND val:%d, ret : %d\n", + __func__, payload, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_SOFT_STEREO_BLEND: + payload = radio->low->fm_state.use_switched_blend ? + MODE_MASK_BLEND : 0; + FDEBUG(radio, "%s(), MOST_BLEND val:%d, ret : %d\n", + __func__, ctrl->val, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF: + payload = radio->low->fm_config.blend_coeffs_soft; + FDEBUG(radio, "%s(),BLEND_COEFF_SOFT val:%d, ret: %d\n", + __func__, ctrl->val, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_SOFT_MUTE_COEFF: + payload = radio->low->fm_config.mute_coeffs_soft; + FDEBUG(radio, "%s(), MUTE_COEFF_SOFT val:%d, ret: %d\n", + __func__, ctrl->val, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_RSSI_CURR: + fm_update_rssi(radio); + ctrl->val = radio->low->fm_state.rssi; + FDEBUG(radio, "%s(), RSSI_CURR val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_SNR_CURR: + radio->low->fm_state.snr = fmspeedy_get_reg(0xFFF2C5); + payload = radio->low->fm_state.snr; + FDEBUG(radio, "%s(), SNR_CURR val:%d, ret : %d\n", + __func__, payload, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_SEEK_CANCEL: + ctrl->val = 0; + break; + case V4L2_CID_S610_SEEK_MODE: + if (radio->seek_mode == FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT) + ctrl->val = 4; + else + ctrl->val = radio->seek_mode; + + FDEBUG(radio, "%s(), SEEK_MODE val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_RDS_ON: + if (radio->low->fm_state.fm_pwr_on & S610_POWER_ON_RDS) + ctrl->val = FM_RDS_ENABLE; + else + ctrl->val = FM_RDS_DISABLE; + FDEBUG(radio, "%s(), RDS_ON:%d, ret: %d\n", __func__, + ctrl->val, ret); + break; + case V4L2_CID_S610_IF_COUNT1: + payload = radio->low->fm_config.search_conf.weak_ifca_m; + FDEBUG(radio, "%s(), IF_CNT1 val:%d, ret : %d\n", + __func__, ctrl->val, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_IF_COUNT2: + payload = radio->low->fm_config.search_conf.normal_ifca_m; + FDEBUG(radio, "%s(), IF_CNT2 val:%d, ret : %d\n", + __func__, ctrl->val, ret); + ctrl->val = payload; + break; + case V4L2_CID_S610_RSSI_TH: + ctrl->val = radio->low->fm_state.rssi; + FDEBUG(radio, "%s(), RSSI_CURR val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_KERNEL_VER: + ctrl->val |= FM_RADIO_RDS_PARSER_VER_CHECK; + FDEBUG(radio, "%s(), KERNEL_VER val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_SOFT_STEREO_BLEND_REF: + ctrl->val = radio->rssi_ref_enable; + FDEBUG(radio, "%s(), SOFT_STEREO_BLEND_REF val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + + default: + ret = -EINVAL; + dev_err(radio->v4l2dev.dev, + "g_volatile_ctrl Unknown IOCTL: %d\n", + (int) ctrl->id); + break; + } + + s610_core_unlock(radio->core); + FUNC_EXIT(radio); + + return ret; +} + +static int s610_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + u16 payload = 0; + struct s610_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + FUNC_ENTRY(radio); + + s610_core_lock(radio->core); + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: /* set mute */ + ret = fm_set_mute_mode(radio, (u8)ctrl->val); + FDEBUG(radio, "%s(), mute val:%d, ret : %d\n", __func__, + ctrl->val, ret); + if (ret != 0) + ret = -EIO; + break; + case V4L2_CID_AUDIO_VOLUME: /* set volume gain */ + fm_set_audio_gain(radio, (u8)ctrl->val); + FDEBUG(radio, "%s(), volume val:%d, ret : %d\n", __func__, + ctrl->val, ret); + break; + case V4L2_CID_TUNE_DEEMPHASIS: + if (ctrl->val == 0) + break; + payload = (u16)ctrl->val; + if (ctrl->val > 0) + payload -= 1; + ret = fm_set_deemphasis(radio, payload); + FDEBUG(radio, "%s(), deemphasis val:%d, ret : %d, payload:%d\n", __func__, + ctrl->val, ret, payload); + if (ret != 0) + ret = -EINVAL; + break; + case V4L2_CID_S610_CH_SPACING: + radio->freq_step = ctrl->val; + radio->low->fm_tuner_state.freq_step = + radio->low->fm_freq_steps[ctrl->val]; + FDEBUG(radio, "%s(), FREQ_STEP val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_CH_BAND: + radio_region = ctrl->val; + radio->radio_region = ctrl->val; + payload = ctrl->val; + fm_set_band(radio, payload); + FDEBUG(radio, "%s(), BAND val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_SOFT_STEREO_BLEND: + ret = low_set_most_blend(radio, ctrl->val); + FDEBUG(radio, "%s(), MOST_BLEND val:%d, ret : %d\n", + __func__, ctrl->val, ret); + if (ret != 0) + ret = -EIO; + break; + case V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF: + radio->low->fm_config.blend_coeffs_soft = (u16)ctrl->val; + fm_set_blend_mute(radio); + FDEBUG(radio, "%s(), BLEND_COEFF_SOFT val:%d,ret: %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_SOFT_MUTE_COEFF: + radio->low->fm_config.mute_coeffs_soft = (u16)ctrl->val; + fm_set_blend_mute(radio); + FDEBUG(radio, "%s(), SOFT_MUTE_COEFF val:%d, ret: %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_RSSI_CURR: + ctrl->val = 0; + break; + case V4L2_CID_S610_SEEK_CANCEL: + if (ctrl->val) { + payload = FM_TUNER_STOP_SEARCH_MODE; + low_set_tuner_mode(radio, payload); + FDEBUG(radio, "%s(), SEEK_CANCEL val:%d, ret: %d\n", + __func__, ctrl->val, ret); + radio->seek_mode = FM_TUNER_STOP_SEARCH_MODE; + } + break; + case V4L2_CID_S610_SEEK_MODE: + if (ctrl->val == 4) + radio->seek_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT; + else + radio->seek_mode = ctrl->val; + + FDEBUG(radio, "%s(), SEEK_MODE val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_RDS_ON: + payload = (u16)ctrl->val; + if (payload & RDS_PARSER_ENABLE) { + radio->rds_parser_enable = TRUE; + } else { + radio->rds_parser_enable = FALSE; + } + payload &= FM_RDS_ENABLE; + ret = fm_set_rds_mode(radio, payload); + FDEBUG(radio, "%s(), RDS_RECEPTION:%d, ret:%d parser:%d\n", __func__, + payload, ret, radio->rds_parser_enable); + if (ret != 0) + ret = -EINVAL; + break; + case V4L2_CID_S610_SNR_CURR: + ctrl->val = 0; + break; + case V4L2_CID_S610_IF_COUNT1: + radio->low->fm_config.search_conf.weak_ifca_m = + (u16)ctrl->val; + FDEBUG(radio, "%s(), IF_CNT1 val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_IF_COUNT2: + radio->low->fm_config.search_conf.normal_ifca_m = + (u16)ctrl->val; + FDEBUG(radio, "%s(), IF_CNT2 val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_RSSI_TH: + low_set_search_lvl(radio, (u16)ctrl->val); + FDEBUG(radio, "%s(), RSSI_TH val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + case V4L2_CID_S610_KERNEL_VER: + ctrl->val = 0; + break; + case V4L2_CID_S610_SOFT_STEREO_BLEND_REF: + payload = (u16)ctrl->val; + if (payload & RSSI_REF_ENABLE) + radio->rssi_ref_enable = TRUE; + else + radio->rssi_ref_enable = FALSE; + + FDEBUG(radio, "%s(), SOFT_STEREO_BLEND_REF val:%d, ret : %d\n", + __func__, ctrl->val, ret); + break; + + default: + ret = -EINVAL; + dev_err(radio->v4l2dev.dev, "s_ctrl Unknown IOCTL: 0x%x\n", + (int)ctrl->id); + break; + } + s610_core_unlock(radio->core); + FUNC_EXIT(radio); + + return ret; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int s610_radio_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + struct s610_radio *radio = video_drvdata(file); + + FUNC_ENTRY(radio); + + s610_core_lock(radio->core); + + reg->size = 4; + reg->val = (__u64)fmspeedy_get_reg((u32)reg->reg); + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return 0; +} +static int s610_radio_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + struct s610_radio *radio = video_drvdata(file); + + FUNC_ENTRY(radio); + + s610_core_lock(radio->core); + fmspeedy_set_reg((u32)reg->reg, (u32)reg->val); + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return 0; +} +#endif + +int s610_core_set_power_state(struct s610_radio *radio, + u8 next_state) +{ + int ret = 0; + + FUNC_ENTRY(radio); + + ret = low_set_power(radio, next_state); + if (ret != 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for power on\n"); + ret = -EIO; + goto ret_power; + } + radio->core->power_mode |= next_state; + +ret_power: + FUNC_EXIT(radio); + return ret; +} + +void fm_prepare(struct s610_radio *radio) +{ + FUNC_ENTRY(radio); + + spin_lock_init(&radio->rds_buff_lock); + + /* Initial FM device variables */ + /* Region info */ + radio->region = region_configs[radio_region]; + radio->mute_mode = FM_MUTE_OFF; + radio->rf_depend_mute = FM_RX_RF_DEPENDENT_MUTE_OFF; + radio->rds_flag = FM_RDS_DISABLE; + radio->freq = FM_UNDEFINED_FREQ; + radio->rds_mode = FM_RDS_SYSTEM_RDS; + radio->af_mode = FM_RX_RDS_AF_SWITCH_MODE_OFF; + radio->core->power_mode = S610_POWER_DOWN; + radio->seek_weak_rssi = SCAN_WEAK_SIG_THRESHOLD; + /* Reset RDS buffer cache if need */ + + /* Initial wait queue for rds read */ + init_waitqueue_head(&radio->core->rds_read_queue); + radio->idle_cnt_mod = 0; + radio->rds_new_stat = 0; + + FUNC_EXIT(radio); + +} + +#ifdef USE_FM_LNA_ENABLE +static int set_eLNA_gpio(struct s610_radio *radio, int stat) +{ + if (!gpio_is_valid(radio->elna_gpio)) { + return -EINVAL; + } else { + dev_info(radio->v4l2dev.dev, "%s(%d)\n", __func__, stat); + gpio_set_value(radio->elna_gpio, stat); + } + + return 0; +} +#endif /* USE_FM_LNA_ENABLE */ + +static int s610_radio_fops_open(struct file *file) +{ + int ret; + struct s610_radio *radio = video_drvdata(file); + static bool run_once = true; + + FUNC_ENTRY(radio); + +#ifdef CONFIG_SCSC_FM + /* Start FM/WLBT LDO */ + ret = mx250_fm_request(); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "mx250_fm_request() failed with err %d\n", ret); + return ret; + } +#endif /* CONFIG_SCSC_FM */ + + shared_fm_open_cnt++; + +#ifdef USE_FM_LNA_ENABLE + if (radio->elna_gpio != -EINVAL) { + ret = set_eLNA_gpio(radio, GPIO_HIGH); + if (ret) + dev_info(radio->v4l2dev.dev, + "Failed to set gpio for eLNA\n"); + } +#endif /* USE_FM_LNA_ENABLE */ + + ret = v4l2_fh_open(file); + if (ret) + return ret; + + if (v4l2_fh_is_singular_file(file)) { + s610_core_lock(radio->core); + atomic_set(&radio->is_doing, 0); + atomic_set(&radio->is_rds_doing, 0); + +#ifdef USE_AUDIO_PM + if (radio->a_dev) { + ret = pm_runtime_get_sync(radio->a_dev); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "audio get_sync not work: not suspend %d\n", ret); + goto err_open; + } + +// abox_request_cpu_gear_sync(radio->a_dev, +// dev_get_drvdata(radio->a_dev), radio->dev, ABOX_CPU_GEAR_MAX); + } +#endif /* USE_AUDIO_PM */ + + ret = pm_runtime_get_sync(radio->dev); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "get_sync failed with err %d\n", ret); + goto err_open; + } + + fmspeedy_wakeup(); + + if (run_once) { + fm_ds_set(0); + fm_aux_pll_off(); + + run_once = false; + } + + fm_get_version_number(); + + /* Initail fm low structure */ + ret = init_low_struc(radio); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "Failed to init reg for initial struc\n"); + goto err_open; + } + + /* Booting fm */ + ret = fm_boot(radio); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "Failed to set reg for FM boot\n"); + goto err_open; + } + + fm_prepare(radio); + + ret = s610_core_set_power_state(radio, + S610_POWER_ON_FM); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for power on\n"); + goto err_open; + } + + ret = s610_radio_pretune(radio); + if (ret < 0) { + dev_err(radio->v4l2dev.dev, + "Failed to write reg for preturn\n"); + goto power_down; + } + + radio->core->power_mode = S610_POWER_ON_FM; + + mdelay(100); + +#ifndef RDS_POLLING_ENABLE + /* Speedy master interrupt enable */ + fm_speedy_m_int_enable(); +#else + fm_speedy_m_int_disable(); +#endif /*RDS_POLLING_ENABLE*/ + s610_core_unlock(radio->core); + /*Must be done after s610_core_unlock to prevent a deadlock*/ + v4l2_ctrl_handler_setup(&radio->ctrl_handler); + } + + fm_set_audio_gain(radio, 7); + + FUNC_EXIT(radio); + + return ret; + +power_down: + s610_core_set_power_state(radio, + S610_POWER_DOWN); + +err_open: + s610_core_unlock(radio->core); + v4l2_fh_release(file); + + FUNC_EXIT(radio); + + return ret; + +} + +static int s610_radio_fops_release(struct file *file) +{ + int ret = 0; + struct s610_radio *radio = video_drvdata(file); + + FUNC_ENTRY(radio); + + if (v4l2_fh_is_singular_file(file)) { + s610_core_lock(radio->core); + s610_core_set_power_state(radio, S610_POWER_DOWN); + + /* Speedy master interrupt disable */ + fm_speedy_m_int_disable(); + + /* FM demod power off */ + fm_power_off(); + + cancel_delayed_work_sync(&radio->dwork_sig2); + cancel_delayed_work_sync(&radio->dwork_tune); + +#ifdef ENABLE_RDS_WORK_QUEUE + cancel_work_sync(&radio->work); +#endif /*ENABLE_RDS_WORK_QUEUE*/ +#ifdef IDLE_POLLING_ENABLE + fm_idle_periodic_cancel((unsigned long) radio); +#endif /*IDLE_POLLING_ENABLE*/ +#ifdef RDS_POLLING_ENABLE + fm_rds_periodic_cancel((unsigned long) radio); +#endif /*RDS_POLLING_ENABLE*/ +/*#ifdef ENABLE_IF_WORK_QUEUE + cancel_work_sync(&radio->if_work); +#endif*/ /*ENABLE_IF_WORK_QUEUE*/ + + pm_runtime_put_sync(radio->dev); +#ifdef USE_AUDIO_PM + if (radio->a_dev) { +// abox_request_cpu_gear_sync(radio->a_dev, +// dev_get_drvdata(radio->a_dev), radio->dev, ABOX_CPU_GEAR_MIN); + + pm_runtime_put_sync(radio->a_dev); + } +#endif /* USE_AUDIO_PM */ + s610_core_unlock(radio->core); + } + + if (wake_lock_active(&radio->wakelock)) + wake_unlock(&radio->wakelock); + + if (wake_lock_active(&radio->rdswakelock)) + wake_unlock(&radio->rdswakelock); + + ret = v4l2_fh_release(file); + +#ifdef USE_FM_LNA_ENABLE + if (radio->elna_gpio != -EINVAL) { + ret = set_eLNA_gpio(radio, GPIO_LOW); + if (ret) + dev_info(radio->v4l2dev.dev, + "Failed to set gpio for eLNA\n"); + } +#endif /* USE_FM_LNA_ENABLE */ + +#ifdef CONFIG_SCSC_FM + /* Stop FM/WLBT LDO */ + ret = mx250_fm_release(); + if (ret < 0) + dev_err(radio->v4l2dev.dev, + "mx250_fm_release() failed with err %d\n", ret); +#endif + + shared_fm_open_cnt--; + + return ret; +} + +static ssize_t s610_radio_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int ret; + size_t rsize, blocks; + struct s610_radio *radio = video_drvdata(file); + size_t rds_max_thresh; + + FUNC_ENTRY(radio); + + ret = wait_event_interruptible(radio->core->rds_read_queue, + atomic_read(&radio->is_rds_new)); + if (ret < 0) + return -EINTR; + + if (!atomic_read(&radio->is_rds_new)) { + radio->rds_new_stat++; + return -ERESTARTSYS; + } + + if (s610_core_lock_interruptible(radio->core)) + return -ERESTARTSYS; + + if (radio->rds_parser_enable) +#ifdef USE_RINGBUFF_API + rds_max_thresh = ringbuf_bytes_used(&radio->rds_rb); +#else + rds_max_thresh = RDS_MEM_MAX_THRESH_PARSER; +#endif /* USE_RINGBUFF_API */ + else + rds_max_thresh = RDS_MEM_MAX_THRESH; + + /* Turn on RDS mode */ + memset(radio->rds_buf, 0, 480); + + rsize = rds_max_thresh; + rsize = min(rsize, count); + + blocks = rsize/FM_RDS_BLK_SIZE; + + ret = fm_read_rds_data(radio, radio->rds_buf, + (int)rsize, (u16 *)&blocks); + if (ret == 0) { + dev_err(radio->v4l2dev.dev, ">>> Failed to read rds mode\n"); + goto read_unlock; + } + + /* always transfer rds complete blocks */ + if (copy_to_user(buf, &radio->rds_buf, rsize)) + ret = -EFAULT; + + if (radio->rds_parser_enable) { + RDSEBUG(radio, "RDS RD done:%08d:%08d fifo err:%08d type(%02X) len(%02X)", + radio->rds_read_cnt, radio->rds_n_count, radio->rds_fifo_err_cnt, + radio->rds_buf[0], radio->rds_buf[1]); + } else { + if ((radio->rds_buf[2]&0x07) != RDS_BLKTYPE_A) { + radio->rds_reset_cnt++; + if (radio->rds_reset_cnt > radio->low->fm_state.rds_unsync_blk_cnt) { + fm_set_rds_mode(radio, FM_RDS_DISABLE); + mdelay(10); + fm_set_rds_mode(radio, FM_RDS_ENABLE); + radio->rds_reset_cnt = 0; + RDSEBUG(radio, "RDS reset! cause of block type invalid"); + } + RDSEBUG(radio, "RDS block type invalid! %02d, %08d", + (radio->rds_buf[2]&0x07), radio->rds_reset_cnt); + } + + RDSEBUG(radio, "RDS RD done:%08d:%08d fifo err:%08d block type0:%02X,%02X", + radio->rds_read_cnt, radio->rds_n_count, radio->rds_fifo_err_cnt, + (radio->rds_buf[2]&0x11)>>3, radio->rds_buf[2]&0x07); + } + + radio->rds_read_cnt++; + ret = rsize; +read_unlock: + atomic_set(&radio->is_rds_new, 0); + atomic_set(&gradio->is_doing, 0); + atomic_set(&gradio->is_rds_doing, 0); + + s610_core_unlock(radio->core); + + FUNC_EXIT(radio); + + return ret; +} + +static unsigned int s610_radio_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct s610_radio *radio = video_drvdata(file); + unsigned long req_events = poll_requested_events(pts); + unsigned int ret = v4l2_ctrl_poll(file, pts); + + FUNC_ENTRY(radio); + + if (req_events & (POLLIN | POLLRDNORM)) { + poll_wait(file, &radio->core->rds_read_queue, pts); + + if (atomic_read(&radio->is_rds_new)) + ret = POLLIN | POLLRDNORM; + } + + FDEBUG(radio, "POLL RET: 0x%x pwmode: %d freq: %d", + ret, + radio->core->power_mode, + radio->freq); + FUNC_EXIT(radio); + + return ret; +} + +static const struct v4l2_file_operations s610_fops = { + .owner = THIS_MODULE, + .read = s610_radio_fops_read, + .poll = s610_radio_fops_poll, + .unlocked_ioctl = video_ioctl2, + .open = s610_radio_fops_open, + .release = s610_radio_fops_release, +}; + +static const struct v4l2_ioctl_ops S610_ioctl_ops = { + .vidioc_querycap = s610_radio_querycap, + .vidioc_g_tuner = s610_radio_g_tuner, + .vidioc_s_tuner = s610_radio_s_tuner, + + .vidioc_g_frequency = s610_radio_g_frequency, + .vidioc_s_frequency = s610_radio_s_frequency, + .vidioc_s_hw_freq_seek = s610_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = s610_radio_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = s610_radio_g_register, + .vidioc_s_register = s610_radio_s_register, +#endif +}; + +static const struct video_device s610_viddev_template = { + .fops = &s610_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, +}; + +static int s610_radio_add_new_custom(struct s610_radio *radio, + enum s610_ctrl_idx idx, unsigned long flag) +{ + int ret; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, + &s610_ctrls[idx], + NULL); + + ret = radio->ctrl_handler.error; + if (ctrl == NULL && ret) { + dev_err(radio->v4l2dev.dev, + "Could not initialize '%s' control %d\n", + s610_ctrls[idx].name, ret); + return -EINTR; + } + if (flag && (ctrl != NULL)) + ctrl->flags |= flag; + + return ret; +} + +#ifndef RDS_POLLING_ENABLE +static irqreturn_t s610_hw_irq_handle(int irq, void *devid) +{ + struct s610_radio *radio = devid; + u32 int_stat; + + spin_lock(&radio->slock); + + int_stat = __raw_readl(radio->fmspeedy_base+FMSPDY_STAT); + + if (int_stat & FM_SLV_INT) + fm_isr(radio); + + spin_unlock(&radio->slock); + + return IRQ_HANDLED; +} +#endif /* RDS_POLLING_ENABLE */ + +MODULE_ALIAS("platform:s610-radio"); +static const struct of_device_id exynos_fm_of_match[] = { + { + .compatible = "samsung,exynos9610-fm", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_fm_of_match); + +signed int exynos_get_fm_open_status(void) +{ + pr_info("%s(): shared fm open count: %d", __func__, shared_fm_open_cnt); + return (shared_fm_open_cnt); +} + +EXPORT_SYMBOL(exynos_get_fm_open_status); + +/* bit 28: + Global hardware automatic clock gating enable on CMU module : CMU +*/ +#define ENABLE_AUTOMATIC_CLKGATING 28 +void disable_clk_gating(struct s610_radio *radio) +{ + void __iomem *cmu_disaud_800; + u32 cmu_disaud_800_val; + + FUNC_ENTRY(radio); + + cmu_disaud_800 = radio->disaud_cmu_base+0x800; + + /* Read register */ + cmu_disaud_800_val = read32(cmu_disaud_800); + FDEBUG(radio, + "cmu_disaud_800: 0x%08X,\n", + cmu_disaud_800_val); + + if (GetBits(cmu_disaud_800, ENABLE_AUTOMATIC_CLKGATING, 1)) + SetBits(cmu_disaud_800, ENABLE_AUTOMATIC_CLKGATING, 1, 0); + + /* Read register */ + cmu_disaud_800_val = read32(cmu_disaud_800); + FDEBUG(radio, + "cmu_disaud_800: 0x%08X,\n", + cmu_disaud_800_val); + + FUNC_EXIT(radio); +} + +void enable_FM_mux_clk_aud(struct s610_radio *radio) +{ + void __iomem *cmu_disaud_100c; + u32 cmu_disaud_100c_val; + + FUNC_ENTRY(radio); + + cmu_disaud_100c = radio->disaud_cmu_base+0x100C; + + /* Read register */ + cmu_disaud_100c_val = read32(cmu_disaud_100c); + FDEBUG(radio, + "befor: cmu_disaud_100c: 0x%08X,\n", + cmu_disaud_100c_val); + + if (GetBits(cmu_disaud_100c, ENABLE_AUTOMATIC_CLKGATING, 1)) + SetBits(cmu_disaud_100c, ENABLE_AUTOMATIC_CLKGATING, 1, 0); + + /* Read register */ + cmu_disaud_100c_val = read32(cmu_disaud_100c); + FDEBUG(radio, + "after: cmu_disaud_100c: 0x%08X,\n", + cmu_disaud_100c_val); + + FUNC_EXIT(radio); +} + +#ifdef USE_AUDIO_PM +static struct device_node *exynos_audio_parse_dt(struct s610_radio *radio) +{ + struct platform_device *pdev = NULL; + struct device_node *np = NULL; + + np = of_find_compatible_node(NULL, NULL, "samsung,abox"); + if (!np) { + dev_err(radio->dev, "abox device is not available\n"); + return NULL; + } + + pdev = of_find_device_by_node(np); + if (!pdev) { + dev_err(radio->dev, "%s: failed to get audio platform_device\n", __func__); + return NULL; + } + radio->a_dev = &pdev->dev; + + return np; +} +#endif /* USE_AUDIO_PM */ + +static int s610_radio_probe(struct platform_device *pdev) +{ + unsigned long ret; + struct s610_radio *radio; + struct v4l2_ctrl *ctrl; + const struct of_device_id *match; + struct resource *resource; + struct device *dev = &pdev->dev; + static atomic_t instance = ATOMIC_INIT(0); + struct clk *clk; + struct device_node *dnode; +#ifdef USE_FM_LNA_ENABLE + int elna_gpio = 0; +#endif /*USE_FM_LNA_ENABLE*/ + + dnode = dev->of_node; + + dev_info(&pdev->dev, ">>> start FM Radio probe\n"); + + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->core = devm_kzalloc(&pdev->dev, sizeof(struct s610_core), + GFP_KERNEL); + if (!radio->core) { + ret = -ENOMEM; + goto alloc_err0; + } + + radio->low = devm_kzalloc(&pdev->dev, sizeof(struct s610_low), + GFP_KERNEL); + if (!radio->low) { + ret = -ENOMEM; + goto alloc_err1; + } + + clk = devm_clk_get(&pdev->dev, "qch_fm"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto alloc_err2; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to prepare enable clock\n"); + goto alloc_err2; + } + + ret = clk_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto alloc_err2; + } + radio->clk = clk; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + pm_runtime_get_sync(dev); + + spin_lock_init(&radio->slock); + spin_lock_init(&radio->rds_lock); + + /* Init flags FR BL init_completion */ + init_completion(&radio->flags_set_fr_comp); + init_completion(&radio->flags_seek_fr_comp); + + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); + + ret = v4l2_device_register(&pdev->dev, &radio->v4l2dev); + if (ret) { + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); + goto alloc_err3; + } + + match = of_match_node(exynos_fm_of_match, dev->of_node); + if (!match) { + dev_err(&pdev->dev, "failed to match node\n"); + ret = -EINVAL; + goto alloc_err4; + } + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + radio->fmspeedy_base = devm_ioremap_resource(&pdev->dev, resource); + if (IS_ERR(radio->fmspeedy_base)) { + dev_err(&pdev->dev, + "Failed to request memory region\n"); + ret = -EBUSY; + goto alloc_err4; + } + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); + radio->disaud_cmu_base = devm_ioremap_resource(&pdev->dev, resource); + if (IS_ERR(radio->disaud_cmu_base)) { + dev_err(&pdev->dev, + "Failed to request memory region\n"); + ret = -EBUSY; + goto alloc_err4; + } + +#ifndef RDS_POLLING_ENABLE + /*save to global variavle fm speedy physical address */ + resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!resource) { + dev_err(&pdev->dev, "failed to get IRQ resource\n"); + ret = -ENOENT; + goto alloc_err4; + } + + ret = devm_request_irq(&pdev->dev, + resource->start, + s610_hw_irq_handle, + 0, + pdev->name, + radio); + if (ret) { + dev_err(&pdev->dev, "failed to install irq\n"); + goto alloc_err4; + } + radio->irq = resource->start; +#endif /* RDS_POLLING_ENABLE */ + + radio->dev = dev; + radio->pdev = pdev; + gradio = radio; + +#ifdef USE_FM_LNA_ENABLE + elna_gpio = of_get_named_gpio(dnode, "elna_gpio", 0); + if (!gpio_is_valid(elna_gpio)) { + dev_err(dev, "Disable elna_gpio control\n"); + elna_gpio = -EINVAL; + } else { + ret = gpio_request_one(elna_gpio, GPIOF_OUT_INIT_LOW, "LNA_GPIO_EN"); + if (ret) { + dev_err(dev, "Disable elna_gpio control\n"); + elna_gpio = -EINVAL; + } else + dev_info(dev, "Enable elna_gpio control\n"); + } + radio->elna_gpio = elna_gpio; +#endif /* USE_FM_LNA_ENABLE */ + +#ifdef USE_AUDIO_PM + if (!exynos_audio_parse_dt(radio)) { + ret = -EINVAL; + goto alloc_err4; + } +#endif /* USE_AUDIO_PM */ + + memcpy(&radio->videodev, &s610_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = &radio->v4l2dev; + radio->videodev.ioctl_ops = &S610_ioctl_ops; + + video_set_drvdata(&radio->videodev, radio); + platform_set_drvdata(pdev, radio); + + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; + + v4l2_ctrl_handler_init(&radio->ctrl_handler, + 1 + ARRAY_SIZE(s610_ctrls)); + /* Set control */ + ret = s610_radio_add_new_custom(radio, S610_IDX_CH_SPACING, + 0); /*0x01*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_CH_BAND, + 0); /*0x02*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_STEREO_BLEND, + 0); /*0x03*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_STEREO_BLEND_COEFF, + 0); /*0x04*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_MUTE_COEFF, + 0); /*0x05*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_RSSI_CURR, + V4L2_CTRL_FLAG_VOLATILE); /*0x06*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SNR_CURR, + V4L2_CTRL_FLAG_VOLATILE); /*0x07*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SEEK_CANCEL, + V4L2_CTRL_FLAG_EXECUTE_ON_WRITE); /*0x08*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SEEK_MODE, + 0); /*0x09*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_RDS_ON, + 0); /*0x0A*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_IF_COUNT1, + 0); /*0x0B*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_IF_COUNT2, + 0); /*0x0C*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_RSSI_TH, + 0); /*0x0D*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_KERNEL_VER, + V4L2_CTRL_FLAG_VOLATILE); /*0x0E*/ + if (ret < 0) + goto exit; + ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_STEREO_BLEND_REF, + V4L2_CTRL_FLAG_EXECUTE_ON_WRITE|V4L2_CTRL_FLAG_VOLATILE); /*0x0F*/ + if (ret < 0) + goto exit; + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &s610_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 4, 1, 1); + ret = radio->ctrl_handler.error; + if (ctrl == NULL && ret) { + dev_err(&pdev->dev, + "Could not initialize V4L2_CID_AUDIO_MUTE control %d\n", + (int)ret); + goto exit; + } + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &s610_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 1); + ret = radio->ctrl_handler.error; + if (ctrl == NULL && ret) { + dev_err(&pdev->dev, + "Could not initialize V4L2_CID_AUDIO_VOLUME control %d\n", + (int)ret); + goto exit; + } + + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, &s610_ctrl_ops, + V4L2_CID_TUNE_DEEMPHASIS, + V4L2_DEEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_75_uS); + ret = radio->ctrl_handler.error; + if (ctrl == NULL && ret) { + dev_err(&pdev->dev, + "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", + (int)ret); + goto exit; + } + + /* register video device */ + ret = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register video device\n"); + goto exit; + } + + mutex_init(&radio->lock); + s610_core_lock_init(radio->core); + + /*init_waitqueue_head(&radio->core->rds_read_queue);*/ + + INIT_DELAYED_WORK(&radio->dwork_sig2, s610_sig2_work); + INIT_DELAYED_WORK(&radio->dwork_tune, s610_tune_work); +#ifdef RDS_POLLING_ENABLE + INIT_DELAYED_WORK(&radio->dwork_rds_poll, s610_rds_poll_work); +#endif /*RDS_POLLING_ENABLE*/ +#ifdef IDLE_POLLING_ENABLE + INIT_DELAYED_WORK(&radio->dwork_idle_poll, s610_idle_poll_work); +#endif /*IDLE_POLLING_ENABLE*/ + +#ifdef ENABLE_RDS_WORK_QUEUE + INIT_WORK(&radio->work, s610_rds_work); +#endif /*ENABLE_RDS_WORK_QUEUE*/ + +#ifndef RDS_POLLING_ENABLE +#ifdef ENABLE_IF_WORK_QUEUE + INIT_WORK(&radio->if_work, s610_if_work); +#endif /*ENABLE_IF_WORK_QUEUE*/ +#endif /* RDS_POLLING_ENABLE */ + +#ifdef USE_AUDIO_PM + if (radio->a_dev) + pm_runtime_get_sync(radio->a_dev); +#endif /* USE_AUDIO_PM */ + + /* all aux pll off for WIFI/BT */ + radio->rfchip_ver = S620_REV_0; + + ret = of_property_read_u32(dnode, "fm_iclk_aux", &radio->iclkaux); + if (ret) + radio->iclkaux = 1; + dev_info(radio->dev, "iClk Aux: %d\n", radio->iclkaux); + + ret = of_property_read_u32(dnode, "num-tcon-freq", &radio->tc_on); + if (ret) { + radio->tc_on = 0; + goto skip_tc_off; + } + + fm_spur_init = devm_kzalloc(&pdev->dev, radio->tc_on * sizeof(*fm_spur_init), + GFP_KERNEL); + if (!fm_spur_init) { + dev_err(radio->dev, "Mem alloc failed for TC ON freq values, TC off\n"); + radio->tc_on = 0; + goto skip_tc_off; + } + + if (of_property_read_u32_array(dnode, "val-tcon-freq", fm_spur_init, radio->tc_on)) { + dev_err(radio->dev, "Getting val-tcon-freq values faild, TC off\n"); + radio->tc_on = 0; + goto skip_tc_off; + } + dev_info(radio->dev, "number TC On Freq: %d\n", radio->tc_on); +skip_tc_off: + + ret = of_property_read_u32(dnode, "num-trfon-freq", &radio->trf_on); + if (ret) { + radio->trf_on = 0; + goto skip_trf_off; + } + + fm_spur_trf_init = devm_kzalloc(&pdev->dev, radio->trf_on * sizeof(*fm_spur_trf_init), + GFP_KERNEL); + if (!fm_spur_trf_init) { + dev_err(radio->dev, "Mem alloc failed for TRF ON freq values, TRF off\n"); + radio->trf_on = 0; + goto skip_trf_off; + } + + if (of_property_read_u32_array(dnode, "val-trfon-freq", fm_spur_trf_init, radio->trf_on)) { + dev_err(radio->dev, "Getting val-trfon-freq values faild, TRF off\n"); + radio->trf_on = 0; + goto skip_trf_off; + } + dev_info(radio->dev, "number TRF On Freq: %d\n", radio->trf_on); +skip_trf_off: + + ret = of_property_read_u32(dnode, "num-dual-clkon-freq", &radio->dual_clk_on); + if (ret) { + radio->dual_clk_on = 0; + goto skip_dual_clk_off; + } + + fm_dual_clk_init = devm_kzalloc(&pdev->dev, radio->dual_clk_on * sizeof(*fm_dual_clk_init), + GFP_KERNEL); + if (!fm_dual_clk_init) { + dev_err(radio->dev, "Mem alloc failed for DUAL CLK ON freq values, DUAL CLK off\n"); + radio->dual_clk_on = 0; + goto skip_dual_clk_off; + } + + if (of_property_read_u32_array(dnode, "val-dual-clkon-freq", fm_dual_clk_init, radio->dual_clk_on)) { + dev_err(radio->dev, "Getting val-dual-clkon-freq values faild, DUAL CLK off\n"); + radio->dual_clk_on = 0; + goto skip_dual_clk_off; + } + dev_info(radio->dev, "number DUAL CLK On Freq: %d\n", radio->dual_clk_on); +skip_dual_clk_off: + + radio->vol_num = FM_RX_VOLUME_GAIN_STEP; + radio->vol_level_mod = vol_level_init; + + ret = of_property_read_u32(dnode, "num-volume-level", &radio->vol_num); + if (ret) { + goto skip_vol_sel; + } + + radio->vol_level_tmp = devm_kzalloc(&pdev->dev, radio->vol_num * sizeof(u32), + GFP_KERNEL); + if (!radio->vol_level_tmp) { + dev_err(radio->dev, "Mem alloc failed for Volume level values, Volume Level default setting\n"); + goto skip_vol_sel; + } + + if (of_property_read_u32_array(dnode, "val-vol-level", radio->vol_level_tmp, radio->vol_num)) { + dev_err(radio->dev, "Getting val-vol-level values faild, Volume Level default stting\n"); + kfree(radio->vol_level_tmp); + radio->vol_num = FM_RX_VOLUME_GAIN_STEP; + goto skip_vol_sel; + } + radio->vol_level_mod = radio->vol_level_tmp; + +skip_vol_sel: + dev_info(radio->dev, "volume select num: %d\n", radio->vol_num); + + ret = of_property_read_u32(dnode, "vol_3db_on", &radio->vol_3db_att); + if (ret) + radio->vol_3db_att = 0; + dev_info(radio->dev, "volume -3dB: %d\n", radio->vol_3db_att); + + ret = of_property_read_u32(dnode, "rssi_est_on", &radio->rssi_est_on); + if (ret) + radio->rssi_est_on = 0; + dev_info(radio->dev, "rssi_est_on: %d\n", radio->rssi_est_on); + + ret = of_property_read_u32(dnode, "sw_mute_weak", &radio->sw_mute_weak); + if (ret) + radio->sw_mute_weak = 0; + dev_info(radio->dev, "sw_mute_weak: %d\n", radio->sw_mute_weak); + + ret = of_property_read_u32(dnode, "without_elna", &radio->without_elna); + if (ret) + radio->without_elna = 0; + dev_info(radio->dev, "without eLNA: %d\n", radio->without_elna); + + ret = of_property_read_u16(dnode, "rssi_adjust", &radio->rssi_adjust); + if (ret) + radio->rssi_adjust= 0; + dev_info(radio->dev, "rssi adjust: %d\n", radio->rssi_adjust); + +#ifdef USE_AUDIO_PM + if (radio->a_dev) + pm_runtime_put_sync(radio->a_dev); +#endif /* USE_AUDIO_PM */ + + pm_runtime_put_sync(dev); + + wake_lock_init(&radio->wakelock, WAKE_LOCK_SUSPEND, "fm_wake"); + wake_lock_init(&radio->rdswakelock, WAKE_LOCK_SUSPEND, "fm_rdswake"); + + dev_info(&pdev->dev, "end FM probe.\n"); + return 0; + +exit: + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + +alloc_err4: +#ifdef USE_FM_LNA_ENABLE + if (radio->elna_gpio != -EINVAL) + gpio_free(radio->elna_gpio); +#endif /*USE_FM_LNA_ENABLE*/ + + v4l2_device_unregister(&radio->v4l2dev); + +alloc_err3: + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + clk_disable(radio->clk); + clk_unregister(clk); + +alloc_err2: + kfree(radio->low); + +alloc_err1: + kfree(radio->core); + +alloc_err0: + kfree(radio); + + return ret; +} + +static int s610_radio_remove(struct platform_device *pdev) +{ + struct s610_radio *radio = platform_get_drvdata(pdev); + + if (radio) { + clk_disable(radio->clk); + clk_unregister(radio->clk); + clk_put(radio->clk); + + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2dev); +#ifdef USE_FM_LNA_ENABLE + if (radio->elna_gpio != -EINVAL) + gpio_free(radio->elna_gpio); +#endif /*USE_FM_LNA_ENABLE*/ + wake_lock_destroy(&radio->wakelock); + wake_lock_destroy(&radio->rdswakelock); + + kfree(radio->vol_level_tmp); + kfree(radio->low); + kfree(radio->core); + kfree(radio); + } + + return 0; +} + +#define AUD_PLL_RATE_HZ_BYPASS (26000000) +#define AUD_PLL_RATE_HZ_FOR_48000 (1179648040) +static int fm_radio_clk_enable(struct s610_radio *radio) +{ + unsigned long ret = 0; + + if (radio->clk) { + ret = clk_enable(radio->clk); + if (ret) + return ret; + } else { + dev_err(radio->v4l2dev.dev, + "%s: fm radio clk_enable failed\n", __func__); + ret = -EIO; + } + + if (clk_get_rate(radio->clk_pll) <= AUD_PLL_RATE_HZ_BYPASS) { + ret = clk_set_rate(radio->clk_pll, AUD_PLL_RATE_HZ_FOR_48000); + if (IS_ERR_VALUE(ret)) + dev_info(radio->dev, "setting pll clock to 0 is failed: %lu\n", ret); + dev_info(radio->dev, "pll clock: %lu\n", clk_get_rate(radio->clk_pll)); + } + + dev_info(radio->dev, "FM clock: %lu\n", clk_get_rate(radio->clk)); + + return ret; +} + +static void fm_radio_clk_disable(struct s610_radio *radio) +{ + if (radio->clk) { + clk_disable(radio->clk); + } else { + dev_err(radio->v4l2dev.dev, + "%s: fm radio clk_disable failed\n", __func__); + } +} + +#ifdef CONFIG_PM +static int fm_radio_runtime_suspend(struct device *dev) +{ + struct s610_radio *radio = dev_get_drvdata(dev); + + FUNC_ENTRY(radio); + + fm_radio_clk_disable(radio); + + return 0; +} + +static int fm_radio_runtime_resume(struct device *dev) +{ + struct s610_radio *radio = dev_get_drvdata(dev); + int ret = 0; + + FUNC_ENTRY(radio); + + ret = fm_radio_clk_enable(radio); + if (ret) { + dev_err(dev, "%s: clk_enable failed\n", __func__); + return ret; + } + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int fm_radio_suspend(struct device *dev) +{ + struct s610_radio *radio = dev_get_drvdata(dev); + + FUNC_ENTRY(radio); + + if (pm_runtime_suspended(dev)) + return 0; + + fm_radio_clk_disable(radio); + + pm_runtime_put_sync(radio->dev); + + return 0; +} + +static int fm_radio_resume(struct device *dev) +{ + struct s610_radio *radio = dev_get_drvdata(dev); + int ret = 0; + + FUNC_ENTRY(radio); + + ret = pm_runtime_get_sync(radio->dev); + if (ret < 0) { + dev_err(dev, "get_sync failed with err %d\n", ret); + return ret; + } + + ret = fm_radio_clk_enable(radio); + if (ret) { + dev_err(dev, "%s: clk_enable failed\n", __func__); + return ret; + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fm_radio_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fm_radio_suspend, fm_radio_resume) + SET_RUNTIME_PM_OPS(fm_radio_runtime_suspend, + fm_radio_runtime_resume, NULL) +}; + +#define DEV_PM_OPS (&fm_radio_dev_pm_ops) + +static struct platform_driver s610_radio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = exynos_fm_of_match, + .pm = DEV_PM_OPS, + }, + .probe = s610_radio_probe, + .remove = s610_radio_remove, +}; +static int __init init_s610_radio(void) +{ + platform_driver_register(&s610_radio_driver); + + return 0; +} + +static void __exit exit_s610_radio(void) +{ + platform_driver_unregister(&s610_radio_driver); +} + +late_initcall(init_s610_radio); +module_exit(exit_s610_radio); +MODULE_AUTHOR("Youngjoon Chung, "); +MODULE_DESCRIPTION("Driver for S610 FM Radio in Exynos9610"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/radio/s610/radio-s610.h b/drivers/media/radio/s610/radio-s610.h new file mode 100644 index 000000000000..e99754a2d470 --- /dev/null +++ b/drivers/media/radio/s610/radio-s610.h @@ -0,0 +1,553 @@ +#ifndef RADIO_S610_H +#define RADIO_S610_H + +#define DRIVER_NAME "s610-radio" +#define DRIVER_CARD "S610 FM Receiver" + +#define ENABLE_RDS_WORK_QUEUE +#undef ENABLE_RDS_WORK_QUEUE + +#define ENABLE_IF_WORK_QUEUE +/*#undef ENABLE_IF_WORK_QUEUE*/ + +#define USE_FM_LNA_ENABLE +/*#undef USE_FM_LNA_ENABLE*/ + +#define RDS_POLLING_ENABLE + +#define IDLE_POLLING_ENABLE + +#define USE_AUDIO_PM + +/* DEBUG :: Print debug for debug *******/ +#define SUPPORT_FM_DEBUG +#define SUPPORT_API_DEBUG +#undef SUPPORT_FM_DEBUG +#undef SUPPORT_API_DEBUG + +#ifdef SUPPORT_FM_DEBUG +#define FDEBUG(fm, fmt, args...) dev_info(fm->dev, fmt, ##args) +#define FUNC_ENTRY(fm) dev_info(fm->dev, "+ %s(): entry\n", __func__) +#define FUNC_EXIT(fm) dev_info(fm->dev, "- %s(): exit\n", __func__) +#else +#define FDEBUG(fm, fmt, args...) +#define FUNC_ENTRY(fm) +#define FUNC_EXIT(fm) +#endif /*SUPPORT_FM_DEBUG*/ + +#ifdef SUPPORT_API_DEBUG +#define API_ENTRY(fm) dev_info(fm->dev, "> API: %s(): entry\n", __func__) +#define API_EXIT(fm) dev_info(fm->dev, "< API: %s(): exit\n", __func__) +#define APIEBUG(fm, fmt, args...) dev_info(fm->dev, fmt, ##args) +#else +#define API_ENTRY(fm) +#define API_EXIT(fm) +#define APIEBUG(fm, fmt, args...) +#endif + +#define SUPPORT_RDS_DEBUG +#undef SUPPORT_RDS_DEBUG + +#ifdef SUPPORT_RDS_DEBUG +#define RDSEBUG(fm, fmt, args...) dev_info(fm->dev, fmt, ##args) +#define RDS_ENTRY(fm) dev_info(fm->dev, "> RDS: %s(): entry\n", __func__) +#define RDS_EXIT(fm) dev_info(fm->dev, "< RDS: %s(): exit\n", __func__) +#else +#define RDSEBUG(fm, fmt, args...) +#define RDS_ENTRY(fm) +#define RDS_EXIT(fm) +#endif /*SUPPORT_RDS_DEBUG*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "fm_low_struc.h" + +#define V4L2_CID_USER_S610_BASE (0x00980900 + 0x1070) +enum s610_ctrl_id { + V4L2_CID_S610_CH_SPACING = (V4L2_CID_USER_S610_BASE + 0x01), + V4L2_CID_S610_CH_BAND = (V4L2_CID_USER_S610_BASE + 0x02), + V4L2_CID_S610_SOFT_STEREO_BLEND = (V4L2_CID_USER_S610_BASE + 0x03), + V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF = (V4L2_CID_USER_S610_BASE+0x04), + V4L2_CID_S610_SOFT_MUTE_COEFF = (V4L2_CID_USER_S610_BASE + 0x5), + V4L2_CID_S610_RSSI_CURR = (V4L2_CID_USER_S610_BASE + 0x06), + V4L2_CID_S610_SNR_CURR = (V4L2_CID_USER_S610_BASE + 0x07), + V4L2_CID_S610_SEEK_CANCEL = (V4L2_CID_USER_S610_BASE + 0x08), + V4L2_CID_S610_SEEK_MODE = (V4L2_CID_USER_S610_BASE + 0x09), + V4L2_CID_S610_RDS_ON = (V4L2_CID_USER_S610_BASE + 0x0A), + V4L2_CID_S610_IF_COUNT1 = (V4L2_CID_USER_S610_BASE + 0x0B), + V4L2_CID_S610_IF_COUNT2 = (V4L2_CID_USER_S610_BASE + 0x0C), + V4L2_CID_S610_RSSI_TH = (V4L2_CID_USER_S610_BASE + 0x0D), + V4L2_CID_S610_KERNEL_VER = (V4L2_CID_USER_S610_BASE + 0x0E), + V4L2_CID_S610_SOFT_STEREO_BLEND_REF = (V4L2_CID_USER_S610_BASE+0x0F), +}; + +enum fm_flag_get { + FM_EVENT_TUNED = (1 << 0), + FM_EVENT_BD_LMT = (1 << 1), + FM_EVENT_SYN_LOS = (1 << 2), + FM_EVENT_BUF_FUL = (1 << 3), + FM_EVENT_AUD_PAU = (1 << 4), + FM_EVENT_CH_STAT = (1 << 5) +}; + +/* Tunner modes */ +enum fm_tuner_mode { + FM_TUNER_STOP_SEARCH_MODE = 0, + FM_TUNER_PRESET_MODE = 1, + FM_TUNER_AUTONOMOUS_SEARCH_MODE = 2, + FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT = 10 +}; + +/* channel spacing */ +enum fm_channel_spacing { + FM_CHANNEL_SPACING_50KHZ = 1, + FM_CHANNEL_SPACING_100KHZ = 2, + FM_CHANNEL_SPACING_200KHZ = 4 +}; + +#define FM_FREQ_MUL 50 + +/* Mute modes */ +enum fm_mute_mode { + FM_MUTE_ON = 0, + FM_MUTE_OFF = 1, + FM_MUTE_ATTENUATE = 2 +}; + +/* Register set mute bits */ +enum fm_reg_mute { + FM_RX_UNMUTE_MODE = 0x00, + FM_RX_RF_DEP_MODE = 0x01, + FM_RX_AC_MUTE_MODE = 0x02, + FM_RX_HARD_MUTE_LEFT_MODE = 0x04, + FM_RX_HARD_MUTE_RIGHT_MODE = 0x08, + FM_RX_SOFT_MUTE_FORCE_MODE = 0x10 +}; + +/* FM RDS modes */ +enum fm_rds_mode { + FM_RDS_DISABLE = 0, + FM_RDS_ENABLE = 1 +}; + +/* FM RDS Parser */ +#define MAX_PS 8 +#define MAX_RT 64 +#define MAX_RTP_TAG 2 + +struct rtp_tag_info { + u32 content_type; + u32 start_pos; + u32 len; +}; + +struct rtp_info { + u8 toggle; + u8 running; + u8 validated; + struct rtp_tag_info tag[2]; +}; + +struct fm_rds_parser_info { + u32 pi_idx; + u16 pi_buf[2]; + + u32 ecc_idx; + u8 ecc_buf[2]; + + u32 af_idx; + u16 af_buf[2]; + + u8 ps_segment; + u8 ps_len; + u32 ps_idx; + u8 ps_buf[3][MAX_PS + 1]; + u8 ps_err[3][MAX_PS / 2]; + u8 ps_candidate[MAX_PS + 1]; + + u8 rt_segment; + u8 rt_len; + u32 rt_idx; + u8 rt_buf[3][MAX_RT + 1]; + u8 rt_err[3][MAX_RT / 2]; + u8 rt_candidate[MAX_RT + 1]; + u8 rt_change; + u8 rt_validated; + + u8 rtp_drop_blk; + u8 rtp_code_group; + u16 rtp_raw_data[3]; + struct rtp_info rtp_data; + + u8 grp; + bool drop_blk; + u8 rds_event; +}; + +/* RF dependent mute mode */ +#define FM_RX_RF_DEPENDENT_MUTE_ON 1 +#define FM_RX_RF_DEPENDENT_MUTE_OFF 0 + +#define FM_DRV_TURN_TIMEOUT (5*HZ) /* 5 seconds */ +#define FM_DRV_SEEK_TIMEOUT (20*HZ) /* 10 seconds */ + +/* Min and Max volume */ +#define FM_RX_VOLUME_MIN 0 +#define FM_RX_VOLUME_MAX 70 + +/* Volume gain step */ +#define FM_RX_VOLUME_GAIN_STEP 16 + +#define FM_SEARCH_DIRECTION_UP 0 +#define FM_SEARCH_DIRECTION_DOWN 1 + +/* undefined freq */ +#define FM_UNDEFINED_FREQ 0xFFFFFFFF + +/* RDS system type (RDS/RBDS) */ +#define FM_RDS_SYSTEM_RDS 0 +#define FM_RDS_SYSTEM_RBDS 1 + +/* AF on/off */ +#define FM_RX_RDS_AF_SWITCH_MODE_ON 1 +#define FM_RX_RDS_AF_SWITCH_MODE_OFF 0 + +/* RX RDS */ +#define FM_RX_RDS_FLUSH_FIFO 0x1 +#define FM_RX_RDS_FIFO_THRESHOLD 48 /* tuples */ +#define FM_RX_RDS_FIFO_THRESHOLD_PARSER 100 /* tuples */ +#define FM_RDS_BLK_SIZE 3 /* 3 bytes */ + +/* default RSSI value for init */ +#define FM_DEFAULT_RSSI_THRESHOLD 0x8E + +/* Reset pre-tune value */ +#define RESET_PRETUNE_VALUE 103500 + +#define GPIO_LOW 0 +#define GPIO_HIGH 1 + +struct s610_radio; + +enum s610_power_state { + S610_POWER_DOWN = 0, + S610_POWER_ON_FM = 1, + S610_POWER_ON_RDS = 2 +}; + +/* FM region (Europe/US, Japan) info */ +struct region_info { + u32 chanl_space; + u32 bot_freq; + u32 top_freq; + u8 fm_band; +}; + +struct s610_core { + int chip_id; + struct mutex cmd_lock; /* for serializing fm radio operations */ +/* enum s610_power_state power_state;*/ + u8 power_mode; + wait_queue_head_t rds_read_queue; +}; + +/** + * s610_core_lock() - lock the core device to get an exclusive access + * to it. + */ +static inline void s610_core_lock_init(struct s610_core *core) +{ + mutex_init(&core->cmd_lock); +} + +/** + * s610_core_lock() - lock the core device to get an exclusive access + * to it. + */ +static inline void s610_core_lock(struct s610_core *core) +{ + mutex_lock(&core->cmd_lock); +} + +/** + * s610_core_lock() - lock the core device to get an exclusive access + * to it. + */ +static inline int __must_check s610_core_lock_interruptible( + struct s610_core *core) +{ + int ret; + + ret = mutex_lock_interruptible(&core->cmd_lock); + return ret; +} + +/** + * s610_core_unlock() - unlock the core device to relinquish an + * exclusive access to it. + */ +static inline void s610_core_unlock(struct s610_core *core) +{ + mutex_unlock(&core->cmd_lock); +} + +static inline int api_to_real(int freq) +{ + return freq; +} + +static inline int real_to_api(int freq) +{ + return freq; +} + +#define FREQ_MUL (10000000 / 625) + +enum s610_freq_bands { + S610_BAND_FM, + S610_BAND_AM, +}; + +struct ringbuf_t { + u8 *buf; + u8 *head, *tail; + int size; +}; + +/** + * struct s610_radio - radio device + * + * @core: Pointer to underlying core device + * @videodev: Pointer to video device created by V4L2 subsystem + * @ops: Vtable of functions. See struct s610_radio_ops for details + * @kref: Reference counter + * @core_lock: An r/w semaphore to brebvent the deletion of underlying + * core structure is the radio device is being used + */ +struct s610_radio { + struct v4l2_device v4l2dev; + struct video_device videodev; + struct v4l2_ctrl_handler ctrl_handler; + + struct s610_core *core; + struct s610_low *low; + + u32 audmode; + + int radio_region; + struct region_info region; /* Current selected band */ + u8 mute_mode; /* Current mute mode */ + u32 freq; /* Current RX frquency */ + u8 deemphasis_mode; /* Current deemphasis mode */ + u8 rf_depend_mute; /* RF dependent soft mute mode */ + u16 volume; /* Current volume level */ + u16 rssi_threshold; /* Current RSSI threshold level */ + u8 rds_mode; /* RDS operation mode (RDS/RDBS) */ + u8 af_mode; /* Alternate frequency on/off */ + + u16 irq_flag; /* FM interrupt flag */ + u16 irq_mask; /* FM interrupt mask */ + unsigned int irq; /* AP interrupt line */ + + /* flags FR BL completion handler */ + struct completion flags_set_fr_comp; + struct completion flags_seek_fr_comp; + + /* set if counter completion handler */ + struct completion set_if_cnt_comp; + + spinlock_t slock; /* To protect access to buffer */ + spinlock_t rds_lock; /* To protect access to buffer */ + + struct wake_lock wakelock; + struct wake_lock rdswakelock; + atomic_t is_doing; + atomic_t is_rds_new; + atomic_t is_rds_doing; + int wait_atomic; + int wait_atomic_rds; + + spinlock_t rds_buff_lock; /* To protect access to RDS buffer */ + u8 rds_flag; /* RX RDS on/off status */ + u8 rds_new; /* RX RDS new data status */ + u8 rds_buf[480]; + struct ringbuf_t rds_rb; + struct fm_rds_parser_info pi; + struct mutex lock; + struct workqueue_struct *work_queue; + struct workqueue_struct *if_work_queue; + +#ifdef ENABLE_RDS_WORK_QUEUE + struct work_struct work; +#endif /*ENABLE_RDS_WORK_QUEUE*/ +#ifdef ENABLE_IF_WORK_QUEUE + struct work_struct if_work; +#endif /*ENABLE_IF_WORK_QUEUE*/ + + struct delayed_work dwork_sig2; + struct delayed_work dwork_tune; +#ifdef RDS_POLLING_ENABLE + struct delayed_work dwork_rds_poll; +#endif /*RDS_POLLING_ENABLE*/ +#ifdef IDLE_POLLING_ENABLE + struct delayed_work dwork_idle_poll; +#endif /*IDLE_POLLING_ENABLE*/ + u16 dwork_idle_counter; + u16 dwork_rds_counter; + u16 dwork_sig2_counter; + u16 dwork_tune_counter; + u16 switch_rssi; + u16 idle_fniarg; + u16 sig2_fniarg; + u16 tune_fniarg; + + u16 freq_step; + u8 speedy_error; + u16 seek_mode; + u32 seek_freq; /* seek start frquency */ + u32 seek_status; + int seek_weak_rssi; + u32 wrap_around; + u32 iclkaux; + void __iomem *fmspeedy_base; + void __iomem *disaud_cmu_base; +#ifdef USE_FM_LNA_ENABLE + int elna_gpio; +#endif /* USE_FM_EXTERN_PLL */ + struct platform_device *pdev; + struct device *dev; +#ifdef USE_AUDIO_PM + struct device *a_dev; +#endif /* USE_AUDIO_PM */ + struct clk *clk; + struct clk *clk_pll; + struct clk *mux_aud_fm; + struct clk *clk_aud_fm; + int tc_on; + int trf_on; + int dual_clk_on; + int vol_num; + u32 *vol_level_mod; + u32 *vol_level_tmp; + int vol_3db_att; + int rssi_est_on; + int sw_mute_weak; + u32 rfchip_ver; + int without_elna; + u16 rssi_adjust; + bool rssi_ref_enable; + u32 agc_enable; +/* debug print counter */ + int idle_cnt_mod; + int rds_cnt_mod; + fm_rds_block_type_enum block_seq; + +/* Test RDS log */ + bool invalid_rssi; + int rds_n_count; + int rds_r_count; + int rds_new_stat; + int rds_read_cnt; + int rds_fifo_err_cnt; + int rds_fifo_rd_cnt; + int rds_reset_cnt; + int rds_sync_loss_cnt; + int rb_used; + bool rds_parser_enable; + int rds_gcnt; +/* Test RDS log end */ +}; + +extern bool fm_radio_on(struct s610_radio *radio); +extern void fm_radio_off(struct s610_radio *radio); +extern void fm_rds_on(struct s610_radio *radio); +extern void fm_rds_off(struct s610_radio *radio); +extern void fm_set_band(struct s610_radio *radio, u8 index); +extern int fm_boot(struct s610_radio *radio); +extern void fm_power_off(void); + +extern int low_get_search_lvl(struct s610_radio *radio, u16 *value); +extern u16 fm_get_flags(struct s610_radio *radio); +extern int low_set_if_limit(struct s610_radio *radio, u16 value); +extern int low_set_search_lvl(struct s610_radio *radio, u16 value); +extern int low_set_freq(struct s610_radio *radio, u32 value); +extern int low_set_tuner_mode(struct s610_radio *radio, u16 value); +extern int low_set_mute_state(struct s610_radio *radio, u16 value); +extern int low_set_most_mode(struct s610_radio *radio, u16 value); +extern int low_set_most_blend(struct s610_radio *radio, u16 value); +extern int low_set_pause_lvl(struct s610_radio *radio, u16 value); +extern int low_set_pause_dur(struct s610_radio *radio, u16 value); +extern int low_set_demph_mode(struct s610_radio *radio, u16 value); +extern int low_set_rds_cntr(struct s610_radio *radio, u16 value); +extern int low_set_power(struct s610_radio *radio, u16 value); +extern u8 aggr_rssi_device_to_host(u16 val); +extern void fm_isr(struct s610_radio *radio); +extern void cancel_tuner_timer(struct s610_radio *radio); +extern int init_low_struc(struct s610_radio *radio); +extern void fm_speedy_m_int_enable(void); +extern void fm_speedy_m_int_disable(void); +#ifdef ENABLE_RDS_WORK_QUEUE +extern void s610_rds_work(struct work_struct *work); +#endif /*ENABLE_RDS_WORK_QUEUE*/ +#ifdef ENABLE_IF_WORK_QUEUE +extern void s610_if_work(struct work_struct *work); +#endif /*ENABLE_IF_WORK_QUEUE*/ + +extern void s610_sig2_work(struct work_struct *work); +extern void s610_tune_work(struct work_struct *work); +#ifdef RDS_POLLING_ENABLE +extern void s610_rds_poll_work(struct work_struct *work); +extern void fm_rds_periodic_update(unsigned long data); +extern void fm_rds_periodic_cancel(unsigned long data); +#endif /*RDS_POLLING_ENABLE*/ +#ifdef IDLE_POLLING_ENABLE +extern void s610_idle_poll_work(struct work_struct *work); +extern void fm_idle_periodic_update(unsigned long data); +extern void fm_idle_periodic_cancel(unsigned long data); +#endif /*IDLE_POLLING_ENABLE*/ + +extern void fm_set_blend_mute(struct s610_radio *radio); + +extern u32 fmspeedy_get_reg(u32 addr); +extern int fmspeedy_set_reg(u32 addr, u32 data); +extern u32 fmspeedy_get_reg_field(u32 addr, u32 shift, u32 mask); + +extern void fm_update_rssi(struct s610_radio *radio); +extern void fm_update_snr(struct s610_radio *radio); +extern void fm_set_audio_gain(struct s610_radio *radio, u16 gain); +extern void fm_aux_pll_off(void); +extern void fmspeedy_wakeup(void); +extern int fm_read_rds_data(struct s610_radio *radio, u8 *buffer, int size, + u16 *blocks); +extern u32 fmspeedy_get_reg_work(u32 addr); +extern signed int exynos_get_fm_open_status(void); +extern void fm_ds_set(u32 data); +extern void fm_get_version_number(void); +extern int ringbuf_bytes_used(const struct ringbuf_t *rb); +extern void fm_rds_parser_reset(struct fm_rds_parser_info *pi); +#endif /* RADIO_S610_H */ + -- 2.20.1