From 95124ffa067496b56f119783140ea158367adda8 Mon Sep 17 00:00:00 2001 From: dearandrew Date: Sun, 16 Dec 2018 19:03:04 +0800 Subject: [PATCH] radio: RTC6226: Add support for RTC6226 FM chip Add support for Richwave's RTC6226 FM chip based on I2C. Add enable, disable, tune, seek and scan functionality. Signed-off-by: tiantsai chang Git-commit: 7066fd96dfa476c7131cfb791f59a09397017f40 Git-repo: https://gitlab.com/richwavegit/rtc6226/ CRs-Fixed: 2513338 Change-Id: Ib91e5612c6cdbae2200b16dedd048b953b6dce64 Signed-off-by: himta ram --- drivers/media/radio/Kconfig | 13 + drivers/media/radio/Makefile | 1 + drivers/media/radio/rtc6226/Makefile | 7 + .../radio/rtc6226/radio-rtc6226-common.c | 2413 +++++++++++++++++ .../media/radio/rtc6226/radio-rtc6226-i2c.c | 739 +++++ drivers/media/radio/rtc6226/radio-rtc6226.h | 668 +++++ 6 files changed, 3841 insertions(+) create mode 100644 drivers/media/radio/rtc6226/Makefile create mode 100644 drivers/media/radio/rtc6226/radio-rtc6226-common.c create mode 100644 drivers/media/radio/rtc6226/radio-rtc6226-i2c.c create mode 100644 drivers/media/radio/rtc6226/radio-rtc6226.h diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index eb79d99787bd..f52db558a38c 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -506,4 +506,17 @@ config RADIO_ZOLTRIX_PORT endif # V4L_RADIO_ISA_DRIVERS +config I2C_RTC6226_QCA + tristate "Richwave RTC6226 FM Radio Receiver support with I2C for QCA" + depends on I2C && RADIO_RTC6226_QCA + ---help--- + This is a driver for I2C devices with the Richwave RTC6226 + chip. + + Say Y here if you want to connect this type of radio to your + computer's I2C port. + + To compile this driver as a module, choose M here: the + module will be called radio-i2c-RTC6226_QCA. + endif # RADIO_ADAPTERS diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 53c7ae135460..bbb8eb0e2d5e 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -34,5 +34,6 @@ 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_I2C_RTC6226_QCA) += rtc6226/ shark2-objs := radio-shark2.o radio-tea5777.o diff --git a/drivers/media/radio/rtc6226/Makefile b/drivers/media/radio/rtc6226/Makefile new file mode 100644 index 000000000000..90057596929c --- /dev/null +++ b/drivers/media/radio/rtc6226/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for radios with Richwave RTC6226 FM Tuner +# + +radio-i2c-rtc6226-qca-objs := radio-rtc6226-i2c.o radio-rtc6226-common.o + +obj-$(CONFIG_I2C_RTC6226_QCA) += radio-i2c-rtc6226-qca.o diff --git a/drivers/media/radio/rtc6226/radio-rtc6226-common.c b/drivers/media/radio/rtc6226/radio-rtc6226-common.c new file mode 100644 index 000000000000..ae8c75e172e8 --- /dev/null +++ b/drivers/media/radio/rtc6226/radio-rtc6226-common.c @@ -0,0 +1,2413 @@ +/* drivers/media/radio/rtc6226/radio-rtc6226-common.c + * + * Driver for Richwave RTC6226 FM Tuner + * + * Copyright (c) 2009 Tobias Lorenz + * Copyright (c) 2012 Hans de Goede + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * 2008-01-12 Tobias Lorenz + * Version 1.0.0 + * - First working version + * 2008-01-13 Tobias Lorenz + * Version 1.0.1 + * - Improved error handling, every function now returns errno + * - Improved multi user access (start/mute/stop) + * - Channel doesn't get lost anymore after start/mute/stop + * - RDS support added (polling mode via interrupt EP 1) + * - marked default module parameters with *value* + * - switched from bit structs to bit masks + * - header file cleaned and integrated + * 2008-01-14 Tobias Lorenz + * Version 1.0.2 + * - hex values are now lower case + * - commented USB ID for ADS/Tech moved on todo list + * - blacklisted in hid-quirks.c + * - rds buffer handling functions integrated into *_work, *_read + * - rds_command exchanged against simple retval + * - check for firmware version 15 + * - code order and prototypes still remain the same + * - spacing and bottom of band codes remain the same + * 2008-01-16 Tobias Lorenz + * Version 1.0.3 + * - code reordered to avoid function prototypes + * - switch/case defaults are now more user-friendly + * - unified comment style + * - applied all checkpatch.pl v1.12 suggestions + * except the warning about the too long lines with bit comments + * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl) + * 2008-01-22 Tobias Lorenz + * Version 1.0.4 + * - avoid poss. locking when doing copy_to_user which may sleep + * - RDS is automatically activated on read now + * - code cleaned of unnecessary rds_commands + * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified + * (thanks to Guillaume RAMOUSSE) + * 2008-01-27 Tobias Lorenz + * Version 1.0.5 + * - number of seek_retries changed to tune_timeout + * - fixed problem with incomplete tune operations by own buffers + * - optimization of variables and printf types + * - improved error logging + * 2008-01-31 Tobias Lorenz + * Oliver Neukum + * Version 1.0.6 + * - fixed coverity checker warnings in *_usb_driver_disconnect + * - probe()/open() race by correct ordering in probe() + * - DMA coherency rules by separate allocation of all buffers + * - use of endianness macros + * - abuse of spinlock, replaced by mutex + * - racy handling of timer in disconnect, + * replaced by delayed_work + * - racy interruptible_sleep_on(), + * replaced with wait_event_interruptible() + * - handle signals in read() + * 2008-02-08 Tobias Lorenz + * Oliver Neukum + * Version 1.0.7 + * - usb autosuspend support + * - unplugging fixed + * 2008-05-07 Tobias Lorenz + * Version 1.0.8 + * - hardware frequency seek support + * - afc indication + * - more safety checks, get_freq return errno + * - vidioc behavior corrected according to v4l2 spec + * 2008-10-20 Alexey Klimov + * - add support for KWorld USB FM Radio FM700 + * - blacklisted KWorld radio in hid-core.c and hid-ids.h + * 2008-12-03 Mark Lord + * - add support for DealExtreme USB Radio + * 2009-01-31 Bob Ross + * - correction of stereo detection/setting + * - correction of signal strength indicator scaling + * 2009-01-31 Rick Bronson + * Tobias Lorenz + * - add LED status output + * - get HW/SW version from scratchpad + * 2009-06-16 Edouard Lafargue + * Version 1.0.10 + * - add support for interrupt mode for RDS endpoint, + * instead of polling. + * Improves RDS reception significantly + * 2018-02-01 LG Electronics, Inc. + * 2018-08-19 Richwave Technology Co.Ltd + */ + +/* kernel includes */ +#include +#include +#include "radio-rtc6226.h" +#define SEEK_TIME_OUT_VALUE 13000 +/************************************************************************** + * Module Parameters + **************************************************************************/ + +/* Bottom of Band (MHz) */ +/* 0: 87.5 - 108 MHz (USA, Europe)*/ +/* 1: 76 - 108 MHz (Japan wide band) */ +/* 2: 76 - 90 MHz (Japan) */ +static unsigned short band; +MODULE_PARM_DESC(band, "Band: *0=87.5-108MHz* 1=76-108MHz 2=76-91MHz 3=65-76MHz"); + +/* De-emphasis */ +/* 0: 75 us (USA) */ +/* 1: 50 us (Europe, Australia, Japan) */ +static unsigned short de; +MODULE_PARM_DESC(de, "De-emphasis: *0=75us* 1=50us"); + +/* Tune timeout */ +static unsigned int tune_timeout = 3000; +MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*"); + +/* Seek timeout */ +static unsigned int seek_timeout = SEEK_TIME_OUT_VALUE; +MODULE_PARM_DESC(seek_timeout, "Seek timeout: *13000*"); + +static const struct v4l2_frequency_band bands[] = { + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 87500, + .rangehigh = 108000, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 76000, + .rangehigh = 108000, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 2, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 76000, + .rangehigh = 91000, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 3, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 64000, + .rangehigh = 76000, + .modulation = V4L2_BAND_MODULATION_FM, + }, +}; + +wait_queue_head_t rtc6226_wq; +int rtc6226_wq_flag = NO_WAIT; +#ifdef New_VolumeControl +unsigned short global_volume; +#endif + +void rtc6226_q_event(struct rtc6226_device *radio, + enum rtc6226_evt_t event) +{ + + struct kfifo *data_b; + unsigned char evt = event; + + data_b = &radio->data_buf[RTC6226_FM_BUF_EVENTS]; + + pr_info("%s updating event_q with event %x\n", __func__, event); + if (kfifo_in_locked(data_b, + &evt, + 1, + &radio->buf_lock[RTC6226_FM_BUF_EVENTS])) + wake_up_interruptible(&radio->event_queue); +} + +/* + * rtc6226_set_chan - set the channel + */ +static int rtc6226_set_chan(struct rtc6226_device *radio, unsigned short chan) +{ + int retval; + bool timed_out = false; + unsigned short current_chan = + radio->registers[CHANNEL] & CHANNEL_CSR0_CH; + + pr_info("%s CHAN=%d chan=%d\n", __func__, radio->registers[CHANNEL], + chan); + + /* start tuning */ + radio->registers[CHANNEL] &= ~CHANNEL_CSR0_CH; + radio->registers[CHANNEL] |= CHANNEL_CSR0_TUNE | chan; + retval = rtc6226_set_register(radio, CHANNEL); + if (retval < 0) { + radio->registers[CHANNEL] = current_chan; + goto done; + } + reinit_completion(&radio->completion); + retval = wait_for_completion_timeout(&radio->completion, + msecs_to_jiffies(tune_timeout)); + if (!retval) + timed_out = true; + + if ((radio->registers[STATUS] & STATUS_STD) == 0) + pr_info("%s tune does not complete\n", __func__); + else { + radio->seek_tune_status = TUNE_PENDING; + rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC); + } + if (timed_out) + pr_info("%s tune timed out after %u ms\n", __func__, + tune_timeout); + + /* stop tuning */ + current_chan = radio->registers[CHANNEL] & CHANNEL_CSR0_CH; + + radio->registers[CHANNEL] &= ~CHANNEL_CSR0_TUNE; + retval = rtc6226_set_register(radio, CHANNEL); + if (retval < 0) { + radio->registers[CHANNEL] = current_chan; + goto done; + } + +done: + pr_info("%s exit %d\n", __func__, retval); + return retval; +} + +/* + * rtc6226_get_freq - get the frequency + */ +static int rtc6226_get_freq(struct rtc6226_device *radio, unsigned int *freq) +{ + unsigned int spacing, band_bottom; + unsigned short chan; + unsigned short rssi = 0; + int retval; + + pr_info("%s enter\n", __func__); + spacing = + ((radio->registers[RADIOCFG] & CHANNEL_CSR0_CHSPACE) >> 8) * 10; + band_bottom = + (radio->registers[RADIOSEEKCFG2] & CHANNEL_CSR0_FREQ_BOT) * 10; + + /* read channel */ + retval = rtc6226_get_register(radio, CHANNEL1); + if (retval < 0) { + pr_info("%s fail to get register\n", __func__); + goto end; + } + chan = radio->registers[CHANNEL1] & STATUS_READCH; + #ifdef andrew + retval = rtc6226_get_register(radio, RSSI); + if (retval < 0) { + pr_err("%s read fail to RSSI\n", __func__); + goto end; + } + + rssi = radio->registers[RSSI] & RSSI_RSSI; + #endif + + pr_info("%s chan %d\n", __func__, chan); + *freq = chan * 10; + pr_info("FMRICHWAVE, freq= %d, rssi= %d dBuV\n", *freq, rssi); + +end: + return retval; +} + + +/* + * rtc6226_set_freq - set the frequency + */ +int rtc6226_set_freq(struct rtc6226_device *radio, unsigned int freq) +{ + unsigned int band_bottom; + unsigned short chan; + unsigned char i; + int retval = 0; + + pr_info("%s enter freq:%d\n", __func__, freq); + + band_bottom = (radio->registers[RADIOSEEKCFG2] & + CHANNEL_CSR0_FREQ_BOT) * 10; + + if (freq < band_bottom) + freq = band_bottom; + + /* Chan = Freq (Mhz) / 10 */ + chan = freq / 10; + + pr_info("%s chan:%d freq:%d band_bottom:%d\n", __func__, + chan, freq, band_bottom); + retval = rtc6226_set_chan(radio, chan); + if (retval < 0) { + pr_info("%s fail to set chan\n", __func__); + goto end; + } + + for (i = 0x12; i < RADIO_REGISTER_NUM; i++) { + retval = rtc6226_get_register(radio, i); + if (retval < 0) { + pr_info("%s fail to get register\n", __func__); + goto end; + } + } + +end: + return retval; +} + + +/* + * rtc6226_set_seek - set seek + */ +static int rtc6226_set_seek(struct rtc6226_device *radio, + unsigned int seek_up, unsigned int seek_wrap) +{ + int retval = 0; + bool timed_out = false; + unsigned short seekcfg1_val = radio->registers[SEEKCFG1]; + int i; + + pr_info("%s enter up:%d wrap:%d, th:%d\n", __func__, seek_up, seek_wrap, + seekcfg1_val); + if (seek_wrap) + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SKMODE; + else + radio->registers[SEEKCFG1] |= SEEKCFG1_CSR0_SKMODE; + + if (seek_up) + radio->registers[SEEKCFG1] |= SEEKCFG1_CSR0_SEEKUP; + else + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEKUP; + + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK; + retval = rtc6226_set_register(radio, SEEKCFG1); + if (retval < 0) { + radio->registers[SEEKCFG1] = seekcfg1_val; + goto done; + } + + /* start seeking */ + radio->registers[SEEKCFG1] |= SEEKCFG1_CSR0_SEEK; + retval = rtc6226_set_register(radio, SEEKCFG1); + if (retval < 0) { + radio->registers[SEEKCFG1] = seekcfg1_val; + goto done; + } + + reinit_completion(&radio->completion); + retval = wait_for_completion_timeout(&radio->completion, + msecs_to_jiffies(seek_timeout)); + if (!retval) { + timed_out = true; + pr_err("%s timeout\n", __func__); + rtc6226_get_all_registers(radio); + for (i = 0; i < 16; i++) + pr_info("%s registers[%d]:%x\n", __func__, i, + radio->registers[i]); + } + + if ((radio->registers[STATUS] & STATUS_STD) == 0) + pr_info(" %s seek does not complete\n", __func__); + if (radio->registers[STATUS] & STATUS_SF) { + pr_info(" %s seek failed / band limit reached\n", __func__); + //retval = -ESPIPE; + } + + /* stop seeking : clear STD*/ + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK; + retval = rtc6226_set_register(radio, SEEKCFG1); + + if (retval == 0 && timed_out) + retval = -EAGAIN; + +done: + pr_info("%s exit %d\n", __func__, retval); + return retval; +} + +static void rtc6226_update_search_list(struct rtc6226_device *radio, int freq) +{ + int temp_freq = freq; + int index = radio->srch_list.num_stations_found; + + temp_freq = temp_freq - + (radio->recv_conf.band_low_limit * TUNE_STEP_SIZE); + temp_freq = temp_freq / 50; + radio->srch_list.rel_freq[index].rel_freq_lsb = GET_LSB(temp_freq); + radio->srch_list.rel_freq[index].rel_freq_msb = GET_MSB(temp_freq); + radio->srch_list.num_stations_found++; +} + +void rtc6226_scan(struct work_struct *work) +{ + struct rtc6226_device *radio; + int current_freq_khz = 0; + struct kfifo *data_b; + int len = 0; + u32 next_freq_khz; + int retval = 0; + + pr_info("%s enter\n", __func__); + + radio = container_of(work, struct rtc6226_device, work_scan.work); + radio->seek_tune_status = SEEK_PENDING; + + retval = rtc6226_get_freq(radio, ¤t_freq_khz); + if (retval < 0) { + pr_err("%s fail to get freq\n", __func__); + goto seek_tune_fail; + } + pr_info("%s cuurent freq %d\n", __func__, current_freq_khz); + + while (1) { + if (radio->is_search_cancelled) { + pr_err("%s: scan cancelled\n", __func__); + if (radio->g_search_mode == SCAN_FOR_STRONG) + goto seek_tune_fail; + else + goto seek_cancelled; + goto seek_cancelled; + } else if (radio->mode != FM_RECV) { + pr_err("%s: FM is not in proper state\n", __func__); + return; + } + + retval = rtc6226_set_seek(radio, SRCH_UP, WRAP_DISABLE); + if (retval < 0) { + pr_err("%s seek fail %d\n", __func__, retval); + goto seek_tune_fail; + } + + if (radio->registers[STATUS] & STATUS_SF) { + pr_err("%s band limit reached. Seek one more.\n", + __func__); + seek_timeout = 1000; + retval = rtc6226_set_seek(radio, SRCH_UP, WRAP_ENABLE); + seek_timeout = SEEK_TIME_OUT_VALUE; + if (retval < 0) { + pr_err("%s seek fail %d\n", __func__, retval); + goto seek_tune_fail; + } + retval = rtc6226_get_freq(radio, &next_freq_khz); + if (retval < 0) { + pr_err("%s fail to get freq\n", __func__); + goto seek_tune_fail; + } + pr_info("%s next freq %d\n", __func__, next_freq_khz); + rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC); + break; + } + + retval = rtc6226_get_freq(radio, &next_freq_khz); + if (retval < 0) { + pr_err("%s fail to get freq\n", __func__); + goto seek_tune_fail; + } + pr_info("%s next freq %d\n", __func__, next_freq_khz); + + retval = rtc6226_get_register(radio, RSSI); + if (retval < 0) { + pr_err("%s read fail to RSSI\n", __func__); + goto seek_tune_fail; + } + + pr_info("%s valid channel %d, rssi %d\n", __func__, + next_freq_khz, radio->registers[RSSI] & RSSI_RSSI); + + if (radio->g_search_mode == SCAN) + rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC); + + if (radio->is_search_cancelled) { + pr_err("%s: scan cancelled\n", __func__); + if (radio->g_search_mode == SCAN_FOR_STRONG) + goto seek_tune_fail; + else + goto seek_cancelled; + goto seek_cancelled; + } else if (radio->mode != FM_RECV) { + pr_err("%s: FM is not in proper state\n", __func__); + return; + } + pr_info("%s update search list %d\n", __func__, next_freq_khz); + if (radio->g_search_mode == SCAN) { + /* sleep for dwell period */ + msleep(radio->dwell_time_sec * 1000); + /* need to queue the event when the seek completes */ + pr_info("%s frequency update list %d\n", __func__, + next_freq_khz/16); + rtc6226_q_event(radio, RTC6226_EVT_SCAN_NEXT); + } else if (radio->g_search_mode == SCAN_FOR_STRONG) { + rtc6226_update_search_list(radio, next_freq_khz); + } + + } + +seek_tune_fail: + if (radio->g_search_mode == SCAN_FOR_STRONG) { + len = radio->srch_list.num_stations_found * 2 + + sizeof(radio->srch_list.num_stations_found); + data_b = &radio->data_buf[RTC6226_FM_BUF_SRCH_LIST]; + kfifo_in_locked(data_b, &radio->srch_list, len, + &radio->buf_lock[RTC6226_FM_BUF_SRCH_LIST]); + rtc6226_q_event(radio, RTC6226_EVT_NEW_SRCH_LIST); + } + pr_err("%s seek tune fail %d\n", __func__, retval); + +seek_cancelled: + rtc6226_q_event(radio, RTC6226_EVT_SEEK_COMPLETE); + radio->seek_tune_status = NO_SEEK_TUNE_PENDING; + pr_err("%s seek cancelled %d\n", __func__, retval); + return; + +} + +int rtc6226_cancel_seek(struct rtc6226_device *radio) +{ + int retval = 0; + + pr_info("%s enter\n", __func__); + mutex_lock(&radio->lock); + + /* stop seeking */ + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK; + retval = rtc6226_set_register(radio, SEEKCFG1); + complete(&radio->completion); + + mutex_unlock(&radio->lock); + radio->is_search_cancelled = true; + + return retval; + +} + +void rtc6226_search(struct rtc6226_device *radio, bool on) +{ + int current_freq_khz; + + current_freq_khz = radio->tuned_freq_khz; + + if (on) { + pr_info("%s: Queuing the work onto scan work q\n", __func__); + queue_delayed_work(radio->wqueue_scan, &radio->work_scan, + msecs_to_jiffies(10)); + } else { + rtc6226_cancel_seek(radio); + rtc6226_q_event(radio, RTC6226_EVT_SEEK_COMPLETE); + } +} + +/* + * rtc6226_start - switch on radio + */ +int rtc6226_start(struct rtc6226_device *radio) +{ + int retval; + u8 i2c_error; + u16 initbuf[] = {0x0000}; + + radio->registers[BANKCFG] = 0x0000; + i2c_error = 0; + /* Keep in case of any unpredicted control */ + /* Set 0x16AA */ + radio->registers[DEVICEID] = 0x16AA; + /* released the I2C from unexpected I2C start condition */ + retval = rtc6226_set_register(radio, DEVICEID); + /* recheck TH : 10 */ + while ((retval < 0) && (i2c_error < 10)) { + retval = rtc6226_set_register(radio, DEVICEID); + i2c_error++; + } + + if (retval < 0) { + pr_err("%s set to fail retval = %d\n", __func__, retval); + /* goto done;*/ + } + msleep(30); + + /* Don't read all between writing 0x16AA and 0x96AA */ + i2c_error = 0; + radio->registers[DEVICEID] = 0x96AA; + retval = rtc6226_set_register(radio, DEVICEID); + /* recheck TH : 10 */ + while ((retval < 0) && (i2c_error < 10)) { + retval = rtc6226_set_register(radio, DEVICEID); + i2c_error++; + } + + if (retval < 0) + pr_err("%s set to fail 0x96AA %d\n", __func__, retval); + msleep(30); + + /* get device and chip versions */ + rtc6226_get_register(radio, DEVICEID); + rtc6226_get_register(radio, CHIPID); + pr_info("%s DeviceID=0x%x ChipID=0x%x Addr=0x%x\n", __func__, + radio->registers[DEVICEID], radio->registers[CHIPID], + radio->client->addr); + + /* Have to update shadow buf from all register */ + retval = rtc6226_get_all_registers(radio); + if (retval < 0) + goto done; + + pr_info("%s rtc6226_power_up1: DeviceID=0x%4.4hx ChipID=0x%4.4hx\n", + __func__, + radio->registers[DEVICEID], radio->registers[CHIPID]); + pr_info("%s rtc6226_power_up2: Reg2=0x%4.4hx Reg3=0x%4.4hx\n", __func__, + radio->registers[MPXCFG], radio->registers[CHANNEL]); + pr_info("%s rtc6226_power_up3: Reg4=0x%4.4hx Reg5=0x%4.4hx\n", __func__, + radio->registers[SYSCFG], radio->registers[SEEKCFG1]); + pr_info("%s rtc6226_power_up4: Reg6=0x%4.4hx Reg7=0x%4.4hx\n", __func__, + radio->registers[POWERCFG], radio->registers[PADCFG]); + pr_info("%s rtc6226_power_up5: Reg8=0x%4.4hx Reg9=0x%4.4hx\n", __func__, + radio->registers[8], radio->registers[9]); + pr_info("%s rtc6226_power_up6: regA=0x%4.4hx RegB=0x%4.4hx\n", __func__, + radio->registers[10], radio->registers[11]); + pr_info("%s rtc6226_power_up7: regC=0x%4.4hx RegD=0x%4.4hx\n", __func__, + radio->registers[12], radio->registers[13]); + pr_info("%s rtc6226_power_up8: regE=0x%4.4hx RegF=0x%4.4hx\n", __func__, + radio->registers[14], radio->registers[15]); + + + pr_info("%s DeviceID=0x%x ChipID=0x%x Addr=0x%x\n", __func__, + radio->registers[DEVICEID], radio->registers[CHIPID], + radio->client->addr); + + /* initial patch 01 */ + initbuf[0] = 0x0038; + retval = rtc6226_set_serial_registers(radio, initbuf, 0x40); + if (retval < 0) + goto done; + + /* initial patch 02 */ + initbuf[0] = 0xC100; + retval = rtc6226_set_serial_registers(radio, initbuf, 0x8E); + if (retval < 0) + goto done; + +done: + return retval; +} + +/* + * rtc6226_stop - switch off radio + */ +int rtc6226_stop(struct rtc6226_device *radio) +{ + int retval; + + /* sysconfig */ + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN; + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN; + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_STDIRQEN; + retval = rtc6226_set_register(radio, SYSCFG); + if (retval < 0) + goto done; + + /* powerconfig */ + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DIS_MUTE; + retval = rtc6226_set_register(radio, MPXCFG); + + /* POWERCFG_ENABLE has to automatically go low */ + radio->registers[POWERCFG] |= POWERCFG_CSR0_DISABLE; + radio->registers[POWERCFG] &= ~POWERCFG_CSR0_ENABLE; + retval = rtc6226_set_register(radio, POWERCFG); + + /* Set 0x16AA */ + radio->registers[DEVICEID] = 0x16AA; + retval = rtc6226_set_register(radio, DEVICEID); + +done: + return retval; +} + +static void rtc6226_get_rds(struct rtc6226_device *radio) +{ + int retval = 0; + int i; + + mutex_lock(&radio->lock); + retval = rtc6226_get_all_registers(radio); + + if (retval < 0) { + pr_err("%s read fail%d\n", __func__, retval); + mutex_unlock(&radio->lock); + return; + } + radio->block[0] = radio->registers[BA_DATA]; + radio->block[1] = radio->registers[BB_DATA]; + radio->block[2] = radio->registers[BC_DATA]; + radio->block[3] = radio->registers[BD_DATA]; + + for (i = 0; i < 4; i++) + pr_info("%s block[%d] %x\n", __func__, i, radio->block[i]); + + radio->bler[0] = (radio->registers[RSSI] & RSSI_RDS_BA_ERRS) >> 14; + radio->bler[1] = (radio->registers[RSSI] & RSSI_RDS_BB_ERRS) >> 12; + radio->bler[2] = (radio->registers[RSSI] & RSSI_RDS_BC_ERRS) >> 10; + radio->bler[3] = (radio->registers[RSSI] & RSSI_RDS_BD_ERRS) >> 8; + mutex_unlock(&radio->lock); +} + +static void rtc6226_pi_check(struct rtc6226_device *radio, u16 current_pi) +{ + if (radio->pi != current_pi) { + pr_info("%s current_pi %x , radio->pi %x\n" + , __func__, current_pi, radio->pi); + radio->pi = current_pi; + } else { + pr_info("%s Received same PI code\n", __func__); + } +} + +static void rtc6226_pty_check(struct rtc6226_device *radio, u8 current_pty) +{ + if (radio->pty != current_pty) { + pr_info("%s PTY code of radio->block[1] = %x\n", + __func__, current_pty); + radio->pty = current_pty; + } else { + pr_info("%s PTY repeated\n", __func__); + } +} + +static bool is_new_freq(struct rtc6226_device *radio, u32 freq) +{ + u8 i = 0; + + for (i = 0; i < radio->af_info2.size; i++) { + if (freq == radio->af_info2.af_list[i]) + return false; + } + + return true; +} + +static bool is_different_af_list(struct rtc6226_device *radio) +{ + u8 i = 0, j = 0; + u32 freq; + + if (radio->af_info1.orig_freq_khz != radio->af_info2.orig_freq_khz) + return true; + + /* freq is same, check if the AFs are same. */ + for (i = 0; i < radio->af_info1.size; i++) { + freq = radio->af_info1.af_list[i]; + for (j = 0; j < radio->af_info2.size; j++) { + if (freq == radio->af_info2.af_list[j]) + break; + } + + /* freq is not there in list2 i.e list1, list2 are different.*/ + if (j == radio->af_info2.size) + return true; + } + + return false; +} + +static bool is_valid_freq(struct rtc6226_device *radio, u32 freq) +{ + u32 band_low_limit; + u32 band_high_limit; + u8 spacing = 0; + + band_low_limit = bands[radio->band].rangelow; + band_high_limit = bands[radio->band].rangehigh; + + if (radio->space == 0) + spacing = CH_SPACING_200; + else if (radio->space == 1) + spacing = CH_SPACING_100; + else if (radio->space == 2) + spacing = CH_SPACING_50; + else + return false; + + if ((freq >= band_low_limit) && + (freq <= band_high_limit) && + ((freq - band_low_limit) % spacing == 0)) + return true; + + return false; +} + +static void rtc6226_update_af_list(struct rtc6226_device *radio) +{ + + bool retval; + u8 i = 0; + u8 af_data = radio->block[2] >> 8; + u32 af_freq_khz; + u32 tuned_freq_khz; + struct kfifo *buff; + struct af_list_ev ev; + spinlock_t lock = radio->buf_lock[RTC6226_FM_BUF_AF_LIST]; + + rtc6226_get_freq(radio, &tuned_freq_khz); + + for (; i < NO_OF_AF_IN_GRP; i++, af_data = radio->block[2] & 0xFF) { + + if (af_data >= MIN_AF_CNT_CODE && af_data <= MAX_AF_CNT_CODE) { + + pr_info("%s: resetting af info, freq %u, pi %u\n", + __func__, tuned_freq_khz, radio->pi); + radio->af_info2.inval_freq_cnt = 0; + radio->af_info2.cnt = 0; + radio->af_info2.orig_freq_khz = 0; + + /* AF count. */ + radio->af_info2.cnt = af_data - NO_AF_CNT_CODE; + radio->af_info2.orig_freq_khz = tuned_freq_khz; + radio->af_info2.pi = radio->pi; + + pr_info("%s: current freq is %u, AF cnt is %u\n", + __func__, tuned_freq_khz, radio->af_info2.cnt); + } else if (af_data >= MIN_AF_FREQ_CODE && + af_data <= MAX_AF_FREQ_CODE && + radio->af_info2.orig_freq_khz != 0 && + radio->af_info2.size < MAX_NO_OF_AF) { + + af_freq_khz = SCALE_AF_CODE_TO_FREQ_KHZ(af_data); + retval = is_valid_freq(radio, af_freq_khz); + if (!retval) { + pr_info("%s: Invalid AF\n", __func__); + radio->af_info2.inval_freq_cnt++; + continue; + } + + retval = is_new_freq(radio, af_freq_khz); + if (!retval) { + pr_info("%s: Duplicate AF\n", __func__); + radio->af_info2.inval_freq_cnt++; + continue; + } + + /* update the AF list */ + radio->af_info2.af_list[radio->af_info2.size++] = + af_freq_khz; + pr_info("%s: AF is %u\n", __func__, af_freq_khz); + if ((radio->af_info2.size + + radio->af_info2.inval_freq_cnt == + radio->af_info2.cnt) && + is_different_af_list(radio)) { + + /* Copy the list to af_info1. */ + radio->af_info1.cnt = radio->af_info2.cnt; + radio->af_info1.size = radio->af_info2.size; + radio->af_info1.pi = radio->af_info2.pi; + radio->af_info1.orig_freq_khz = + radio->af_info2.orig_freq_khz; + memset(radio->af_info1.af_list, 0, + sizeof(radio->af_info1.af_list)); + + memcpy(radio->af_info1.af_list, + radio->af_info2.af_list, + sizeof(radio->af_info2.af_list)); + + /* AF list changed, post it to user space */ + memset(&ev, 0, sizeof(struct af_list_ev)); + + ev.tune_freq_khz = + radio->af_info1.orig_freq_khz; + ev.pi_code = radio->pi; + ev.af_size = radio->af_info1.size; + + memcpy(&ev.af_list[0], + radio->af_info1.af_list, + GET_AF_LIST_LEN(ev.af_size)); + + buff = &radio->data_buf[RTC6226_FM_BUF_AF_LIST]; + kfifo_in_locked(buff, + (u8 *)&ev, + GET_AF_EVT_LEN(ev.af_size), + &lock); + + pr_info("%s: posting AF list evt,currfreq %u\n", + __func__, ev.tune_freq_khz); + + rtc6226_q_event(radio, + RTC6226_EVT_NEW_AF_LIST); + } + } + } +} + +static void rtc6226_update_ps(struct rtc6226_device *radio, u8 addr, u8 ps) +{ + u8 i; + bool ps_txt_chg = false; + bool ps_cmplt = true; + u8 *data; + struct kfifo *data_b; + + pr_info("%s enter addr:%x ps:%x\n", __func__, addr, ps); + + if (radio->ps_tmp0[addr] == ps) { + if (radio->ps_cnt[addr] < PS_VALIDATE_LIMIT) { + radio->ps_cnt[addr]++; + } else { + radio->ps_cnt[addr] = PS_VALIDATE_LIMIT; + radio->ps_tmp1[addr] = ps; + } + } else if (radio->ps_tmp1[addr] == ps) { + if (radio->ps_cnt[addr] >= PS_VALIDATE_LIMIT) { + ps_txt_chg = true; + radio->ps_cnt[addr] = PS_VALIDATE_LIMIT + 1; + } else { + radio->ps_cnt[addr] = PS_VALIDATE_LIMIT; + } + radio->ps_tmp1[addr] = radio->ps_tmp0[addr]; + radio->ps_tmp0[addr] = ps; + } else if (!radio->ps_cnt[addr]) { + radio->ps_tmp0[addr] = ps; + radio->ps_cnt[addr] = 1; + } else { + radio->ps_tmp1[addr] = ps; + } + + if (ps_txt_chg) { + for (i = 0; i < MAX_PS_LEN; i++) { + if (radio->ps_cnt[i] > 1) + radio->ps_cnt[i]--; + } + } + + for (i = 0; i < MAX_PS_LEN; i++) { + if (radio->ps_cnt[i] < PS_VALIDATE_LIMIT) { + pr_info("%s ps_cnt[%d] %d\n", __func__, i, + radio->ps_cnt[i]); + ps_cmplt = false; + return; + } + } + + if (ps_cmplt) { + for (i = 0; (i < MAX_PS_LEN) && + (radio->ps_display[i] == radio->ps_tmp0[i]); i++) + ; + if (i == MAX_PS_LEN) { + pr_info("%s Same PS string repeated\n", __func__); + return; + } + + for (i = 0; i < MAX_PS_LEN; i++) + radio->ps_display[i] = radio->ps_tmp0[i]; + + data = kmalloc(PS_EVT_DATA_LEN, GFP_ATOMIC); + if (data != NULL) { + data[0] = NO_OF_PS; + data[1] = radio->pty; + data[2] = (radio->pi >> 8) & 0xFF; + data[3] = (radio->pi & 0xFF); + data[4] = 0; + memcpy(data + OFFSET_OF_PS, + radio->ps_tmp0, MAX_PS_LEN); + data_b = &radio->data_buf[RTC6226_FM_BUF_PS_RDS]; + kfifo_in_locked(data_b, data, PS_EVT_DATA_LEN, + &radio->buf_lock[RTC6226_FM_BUF_PS_RDS]); + pr_info("%s Q the PS event\n", __func__); + rtc6226_q_event(radio, RTC6226_EVT_NEW_PS_RDS); + kfree(data); + } else { + pr_err("%s Memory allocation failed for PTY\n", + __func__); + } + } +} + +static void display_rt(struct rtc6226_device *radio) +{ + u8 len = 0, i = 0; + u8 *data; + struct kfifo *data_b; + bool rt_cmplt = true; + + pr_info("%s enter\n", __func__); + + for (i = 0; i < MAX_RT_LEN; i++) { + if (radio->rt_cnt[i] < RT_VALIDATE_LIMIT) { + pr_info("%s rt_cnt %d\n", __func__, radio->rt_cnt[i]); + rt_cmplt = false; + return; + } + if (radio->rt_tmp0[i] == END_OF_RT) + break; + } + if (rt_cmplt) { + while ((len < MAX_RT_LEN) && (radio->rt_tmp0[len] != END_OF_RT)) + len++; + + for (i = 0; (i < len) && + (radio->rt_display[i] == radio->rt_tmp0[i]); i++) + ; + if (i == len) { + pr_info("%s Same RT string repeated\n", __func__); + return; + } + for (i = 0; i < len; i++) + radio->rt_display[i] = radio->rt_tmp0[i]; + data = kmalloc(len + OFFSET_OF_RT, GFP_ATOMIC); + if (data != NULL) { + data[0] = len; /* len of RT */ + data[1] = radio->pty; + data[2] = (radio->pi >> 8) & 0xFF; + data[3] = (radio->pi & 0xFF); + data[4] = radio->rt_flag; + memcpy(data + OFFSET_OF_RT, radio->rt_display, len); + data_b = &radio->data_buf[RTC6226_FM_BUF_RT_RDS]; + kfifo_in_locked(data_b, data, OFFSET_OF_RT + len, + &radio->buf_lock[RTC6226_FM_BUF_RT_RDS]); + pr_info("%s Q the RT event\n", __func__); + rtc6226_q_event(radio, RTC6226_EVT_NEW_RT_RDS); + kfree(data); + } else { + pr_err("%s Memory allocation failed for PTY\n", + __func__); + } + } +} + +static void rt_handler(struct rtc6226_device *radio, u8 ab_flg, + u8 cnt, u8 addr, u8 *rt) +{ + u8 i, errcnt, blermax; + bool rt_txt_chg = false; + + pr_info("%s enter\n", __func__); + + if (ab_flg != radio->rt_flag && radio->valid_rt_flg) { + for (i = 0; i < sizeof(radio->rt_cnt); i++) { + if (!radio->rt_tmp0[i]) { + radio->rt_tmp0[i] = ' '; + radio->rt_cnt[i]++; + } + } + memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt)); + memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0)); + memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1)); + } + + radio->rt_flag = ab_flg; + radio->valid_rt_flg = true; + + for (i = 0; i < cnt; i++) { + if ((i < 2) && (cnt > 2)) { + errcnt = radio->bler[2]; + blermax = CORRECTED_THREE_TO_FIVE; + } else { + errcnt = radio->bler[3]; + blermax = CORRECTED_THREE_TO_FIVE; + } + if (errcnt <= blermax) { + if (!rt[i]) + rt[i] = ' '; + if (radio->rt_tmp0[addr+i] == rt[i]) { + if (radio->rt_cnt[addr+i] < RT_VALIDATE_LIMIT) { + radio->rt_cnt[addr+i]++; + } else { + radio->rt_cnt[addr+i] = + RT_VALIDATE_LIMIT; + radio->rt_tmp1[addr+i] = rt[i]; + } + } else if (radio->rt_tmp1[addr+i] == rt[i]) { + if (radio->rt_cnt[addr+i] >= + RT_VALIDATE_LIMIT) { + rt_txt_chg = true; + radio->rt_cnt[addr+i] = + RT_VALIDATE_LIMIT + 1; + } else { + radio->rt_cnt[addr+i] = + RT_VALIDATE_LIMIT; + } + radio->rt_tmp1[addr+i] = radio->rt_tmp0[addr+i]; + radio->rt_tmp0[addr+i] = rt[i]; + } else if (!radio->rt_cnt[addr+i]) { + radio->rt_tmp0[addr+i] = rt[i]; + radio->rt_cnt[addr+i] = 1; + } else { + radio->rt_tmp1[addr+i] = rt[i]; + } + } + } + + if (rt_txt_chg) { + for (i = 0; i < MAX_RT_LEN; i++) { + if (radio->rt_cnt[i] > 1) + radio->rt_cnt[i]--; + } + } + display_rt(radio); +} + +static void rtc6226_raw_rds(struct rtc6226_device *radio) +{ + u16 aid, app_grp_typ; + + aid = radio->block[3]; + app_grp_typ = radio->block[1] & APP_GRP_typ_MASK; + pr_info("%s app_grp_typ = %x\n", __func__, app_grp_typ); + pr_info("%s AID = %x\n", __func__, aid); + + switch (aid) { + case ERT_AID: + radio->utf_8_flag = (radio->block[2] & 1); + radio->formatting_dir = EXTRACT_BIT(radio->block[2], + ERT_FORMAT_DIR_BIT); + if (radio->ert_carrier != app_grp_typ) { + rtc6226_q_event(radio, RTC6226_EVT_NEW_ODA); + radio->ert_carrier = app_grp_typ; + } + break; + case RT_PLUS_AID: + /*Extract 5th bit of MSB (b7b6b5b4b3b2b1b0)*/ + radio->rt_ert_flag = EXTRACT_BIT(radio->block[2], + RT_ERT_FLAG_BIT); + if (radio->rt_plus_carrier != app_grp_typ) { + rtc6226_q_event(radio, RTC6226_EVT_NEW_ODA); + radio->rt_plus_carrier = app_grp_typ; + } + break; + default: + pr_info("%s Not handling the AID of %x\n", __func__, aid); + break; + } +} + +static void rtc6226_ev_ert(struct rtc6226_device *radio) +{ + u8 *data = NULL; + struct kfifo *data_b; + + if (radio->ert_len <= 0) + return; + + pr_info("%s enter\n", __func__); + data = kmalloc((radio->ert_len + ERT_OFFSET), GFP_ATOMIC); + if (data != NULL) { + data[0] = radio->ert_len; + data[1] = radio->utf_8_flag; + data[2] = radio->formatting_dir; + memcpy((data + ERT_OFFSET), radio->ert_buf, radio->ert_len); + data_b = &radio->data_buf[RTC6226_FM_BUF_ERT]; + kfifo_in_locked(data_b, data, (radio->ert_len + ERT_OFFSET), + &radio->buf_lock[RTC6226_FM_BUF_ERT]); + rtc6226_q_event(radio, RTC6226_EVT_NEW_ERT); + kfree(data); + } +} + +static void rtc6226_buff_ert(struct rtc6226_device *radio) +{ + int i; + u16 info_byte = 0; + u8 byte_pair_index; + + byte_pair_index = radio->block[1] & APP_GRP_typ_MASK; + if (byte_pair_index == 0) { + radio->c_byt_pair_index = 0; + radio->ert_len = 0; + } + if (radio->c_byt_pair_index == byte_pair_index) { + for (i = 2; i <= 3; i++) { + info_byte = radio->block[i]; + pr_info("%s info_byte = %x\n", __func__, info_byte); + pr_info("%s ert_len = %x\n", __func__, radio->ert_len); + if (radio->ert_len > (MAX_ERT_LEN - 2)) + return; + radio->ert_buf[radio->ert_len] = radio->block[i] >> 8; + radio->ert_buf[radio->ert_len + 1] = + radio->block[i] & 0xFF; + radio->ert_len += ERT_CNT_PER_BLK; + pr_info("%s utf_8_flag = %d\n", __func__, + radio->utf_8_flag); + if ((radio->utf_8_flag == 0) && + (info_byte == END_OF_RT)) { + radio->ert_len -= ERT_CNT_PER_BLK; + break; + } else if ((radio->utf_8_flag == 1) && + (radio->block[i] >> 8 == END_OF_RT)) { + info_byte = END_OF_RT; + radio->ert_len -= ERT_CNT_PER_BLK; + break; + } else if ((radio->utf_8_flag == 1) && + ((radio->block[i] & 0xFF) + == END_OF_RT)) { + info_byte = END_OF_RT; + radio->ert_len--; + break; + } + } + if ((byte_pair_index == MAX_ERT_SEGMENT) || + (info_byte == END_OF_RT)) { + rtc6226_ev_ert(radio); + radio->c_byt_pair_index = 0; + radio->ert_len = 0; + } + radio->c_byt_pair_index++; + } else { + radio->ert_len = 0; + radio->c_byt_pair_index = 0; + } +} + +static void rtc6226_rt_plus(struct rtc6226_device *radio) +{ + u8 tag_type1, tag_type2; + u8 *data = NULL; + int len = 0; + u16 grp_typ; + struct kfifo *data_b; + + grp_typ = radio->block[1] & APP_GRP_typ_MASK; + /* + *right most 3 bits of Lsb of block 2 + * and left most 3 bits of Msb of block 3 + */ + tag_type1 = (((grp_typ & TAG1_MSB_MASK) << TAG1_MSB_OFFSET) | + (radio->block[2] >> TAG1_LSB_OFFSET)); + /* + *right most 1 bit of lsb of 3rd block + * and left most 5 bits of Msb of 4th block + */ + tag_type2 = (((radio->block[2] & TAG2_MSB_MASK) + << TAG2_MSB_OFFSET) | + (radio->block[2] >> TAG2_LSB_OFFSET)); + + if (tag_type1 != DUMMY_CLASS) + len += RT_PLUS_LEN_1_TAG; + if (tag_type2 != DUMMY_CLASS) + len += RT_PLUS_LEN_1_TAG; + + if (len != 0) { + len += RT_PLUS_OFFSET; + data = kmalloc(len, GFP_ATOMIC); + } else { + pr_err("%s:Len is zero\n", __func__); + return; + } + if (data != NULL) { + data[0] = len; + len = RT_ERT_FLAG_OFFSET; + data[len++] = radio->rt_ert_flag; + if (tag_type1 != DUMMY_CLASS) { + data[len++] = tag_type1; + /* + *start position of tag1 + *right most 5 bits of msb of 3rd block + *and left most bit of lsb of 3rd block + */ + data[len++] = (radio->block[2] >> TAG1_POS_LSB_OFFSET) + & TAG1_POS_MSB_MASK; + /* + *length of tag1 + *left most 6 bits of lsb of 3rd block + */ + data[len++] = (radio->block[2] >> TAG1_LEN_OFFSET) & + TAG1_LEN_MASK; + } + if (tag_type2 != DUMMY_CLASS) { + data[len++] = tag_type2; + /* + *start position of tag2 + *right most 3 bit of msb of 4th block + *and left most 3 bits of lsb of 4th block + */ + data[len++] = (radio->block[3] >> TAG2_POS_LSB_OFFSET) & + TAG2_POS_MSB_MASK; + /* + *length of tag2 + *right most 5 bits of lsb of 4th block + */ + data[len++] = radio->block[3] & TAG2_LEN_MASK; + } + data_b = &radio->data_buf[RTC6226_FM_BUF_RT_PLUS]; + kfifo_in_locked(data_b, data, len, + &radio->buf_lock[RTC6226_FM_BUF_RT_PLUS]); + rtc6226_q_event(radio, RTC6226_EVT_NEW_RT_PLUS); + kfree(data); + } else { + pr_err("%s:memory allocation failed\n", __func__); + } +} + +void rtc6226_rds_handler(struct work_struct *worker) +{ + struct rtc6226_device *radio; + u8 rt_blks[NO_OF_RDS_BLKS]; + u8 grp_type, addr, ab_flg; + + radio = container_of(worker, struct rtc6226_device, rds_worker); + + if (!radio) { + pr_err("%s:radio is null\n", __func__); + return; + } + + pr_info("%s enter\n", __func__); + + rtc6226_get_rds(radio); + + if (radio->bler[0] < CORRECTED_THREE_TO_FIVE) + rtc6226_pi_check(radio, radio->block[0]); + + if (radio->bler[1] < CORRECTED_ONE_TO_TWO) { + grp_type = radio->block[1] >> OFFSET_OF_GRP_TYP; + pr_info("%s grp_type = %d\n", __func__, grp_type); + } else { + pr_err("%s invalid data %d\n", __func__, radio->bler[1]); + return; + } + if (grp_type & 0x01) + rtc6226_pi_check(radio, radio->block[2]); + + rtc6226_pty_check(radio, (radio->block[1] >> OFFSET_OF_PTY) & PTY_MASK); + + switch (grp_type) { + case RDS_TYPE_0A: + if (radio->bler[2] <= CORRECTED_THREE_TO_FIVE) + rtc6226_update_af_list(radio); + /* fall through */ + case RDS_TYPE_0B: + addr = (radio->block[1] & PS_MASK) * NO_OF_CHARS_IN_EACH_ADD; + pr_info("%s RDS is PS\n", __func__); + if (radio->bler[3] <= CORRECTED_THREE_TO_FIVE) { + rtc6226_update_ps(radio, addr+0, radio->block[3] >> 8); + rtc6226_update_ps(radio, addr+1, + radio->block[3] & 0xff); + } + break; + case RDS_TYPE_2A: + pr_info("%s RDS is RT 2A group\n", __func__); + rt_blks[0] = (u8)(radio->block[2] >> 8); + rt_blks[1] = (u8)(radio->block[2] & 0xFF); + rt_blks[2] = (u8)(radio->block[3] >> 8); + rt_blks[3] = (u8)(radio->block[3] & 0xFF); + addr = (radio->block[1] & 0xf) * 4; + ab_flg = (radio->block[1] & 0x0010) >> 4; + rt_handler(radio, ab_flg, CNT_FOR_2A_GRP_RT, addr, rt_blks); + break; + case RDS_TYPE_2B: + pr_info("%s RDS is RT 2B group\n", __func__); + rt_blks[0] = (u8)(radio->block[3] >> 8); + rt_blks[1] = (u8)(radio->block[3] & 0xFF); + rt_blks[2] = 0; + rt_blks[3] = 0; + addr = (radio->block[1] & 0xf) * 2; + ab_flg = (radio->block[1] & 0x0010) >> 4; + radio->rt_tmp0[MAX_LEN_2B_GRP_RT] = END_OF_RT; + radio->rt_tmp1[MAX_LEN_2B_GRP_RT] = END_OF_RT; + radio->rt_cnt[MAX_LEN_2B_GRP_RT] = RT_VALIDATE_LIMIT; + rt_handler(radio, ab_flg, CNT_FOR_2B_GRP_RT, addr, rt_blks); + break; + case RDS_TYPE_3A: + pr_info("%s RDS is 3A group\n", __func__); + rtc6226_raw_rds(radio); + break; + default: + pr_err("%s Not handling the group type %d\n", __func__, + grp_type); + break; + } + pr_info("%s rt_plus_carrier = %x\n", __func__, radio->rt_plus_carrier); + pr_info("%s ert_carrier = %x\n", __func__, radio->ert_carrier); + if (radio->rt_plus_carrier && (grp_type == radio->rt_plus_carrier)) + rtc6226_rt_plus(radio); + else if (radio->ert_carrier && (grp_type == radio->ert_carrier)) + rtc6226_buff_ert(radio); +} + +/* + * rtc6226_rds_on - switch on rds reception + */ +static int rtc6226_rds_on(struct rtc6226_device *radio) +{ + int retval; + + pr_info("%s enter\n", __func__); + /* sysconfig */ + radio->registers[SYSCFG] |= SYSCFG_CSR0_RDS_EN; + retval = rtc6226_set_register(radio, SYSCFG); + + if (retval < 0) + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN; + + return retval; +} + +int rtc6226_reset_rds_data(struct rtc6226_device *radio) +{ + mutex_lock(&radio->lock); + + radio->wr_index = 0; + radio->rd_index = 0; + memset(radio->buffer, 0, radio->buf_size); + mutex_unlock(&radio->lock); + + return 0; +} + +int rtc6226_power_down(struct rtc6226_device *radio) +{ + int retval = 0; + + pr_info("%s enter\n", __func__); + + mutex_lock(&radio->lock); + /* stop radio */ + retval = rtc6226_stop(radio); + + //rtc6226_disable_irq(radio); + mutex_unlock(&radio->lock); + pr_info("%s exit %d\n", __func__, retval); + + return retval; +} + +int rtc6226_power_up(struct rtc6226_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + pr_info("%s enter\n", __func__); + + /* start radio */ + retval = rtc6226_start(radio); + if (retval < 0) + goto done; + pr_info("%s : after initialization\n", __func__); + + /* mpxconfig */ + /* Disable Softmute / Disable Mute / De-emphasis / Volume 8 */ + radio->registers[MPXCFG] = 0x0000 | + MPXCFG_CSR0_DIS_SMUTE | MPXCFG_CSR0_DIS_MUTE | + ((de << 12) & MPXCFG_CSR0_DEEM); + retval = rtc6226_set_register(radio, MPXCFG); + if (retval < 0) + goto done; + + /* enable RDS / STC interrupt */ + radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN; + radio->registers[SYSCFG] |= SYSCFG_CSR0_STDIRQEN; + /*radio->registers[SYSCFG] |= SYSCFG_CSR0_RDS_EN;*/ + retval = rtc6226_set_register(radio, SYSCFG); + if (retval < 0) + goto done; + + radio->registers[PADCFG] &= ~PADCFG_CSR0_GPIO; + radio->registers[PADCFG] |= 0x1 << 2; + retval = rtc6226_set_register(radio, PADCFG); + if (retval < 0) + goto done; + + /* channel */ + /* Top Frequency 108.0MHZ */ + radio->registers[RADIOSEEKCFG1] = 0x2a30; + retval = rtc6226_set_register(radio, RADIOSEEKCFG1); + if (retval < 0) + goto done; + + /* Bottom Frequency 87.5MHz*/ + radio->registers[RADIOSEEKCFG2] = 0x222e; + retval = rtc6226_set_register(radio, RADIOSEEKCFG2); + if (retval < 0) + goto done; + + /* Space 100KHz */ + /* radio->registers[RADIOCFG] &= ~CHANNEL_CSR0_CHSPACE; */ + radio->registers[RADIOCFG] = 0x0a00; + retval = rtc6226_set_register(radio, RADIOCFG); + if (retval < 0) + goto done; + + /* Default channel 90.1Mhz */ + radio->registers[CHANNEL] = 0x2232; + retval = rtc6226_set_register(radio, CHANNEL); + if (retval < 0) + goto done; + + /* I2S salve */ + radio->registers[I2SCFG] = 0x2480; + retval = rtc6226_set_register(radio, I2SCFG); + if (retval < 0) + goto done; + + + /* powerconfig */ + /* Enable FM */ + radio->registers[POWERCFG] = POWERCFG_CSR0_ENABLE; + retval = rtc6226_set_register(radio, POWERCFG); + if (retval < 0) + goto done; + + retval = rtc6226_get_all_registers(radio); + if (retval < 0) + goto done; + + pr_info("%s : DeviceID=0x%4.4hx ChipID=0x%4.4hx\n", __func__, + radio->registers[DEVICEID], radio->registers[CHIPID]); + pr_info("%s : Reg2=0x%4.4hx Reg3=0x%4.4hx\n", __func__, + radio->registers[MPXCFG], radio->registers[CHANNEL]); + pr_info("%s : Reg4=0x%4.4hx Reg5=0x%4.4hx\n", __func__, + radio->registers[SYSCFG], radio->registers[SEEKCFG1]); + pr_info("%s : Reg6=0x%4.4hx Reg7=0x%4.4hx\n", __func__, + radio->registers[POWERCFG], radio->registers[PADCFG]); + pr_info("%s : Reg8=0x%4.4hx Reg9=0x%4.4hx\n", __func__, + radio->registers[8], radio->registers[9]); + pr_info("%s : regA=0x%4.4hx RegB=0x%4.4hx\n", __func__, + radio->registers[10], radio->registers[11]); + pr_info("%s : regC=0x%4.4hx RegD=0x%4.4hx\n", __func__, + radio->registers[12], radio->registers[13]); + pr_info("%s : regE=0x%4.4hx RegF=0x%4.4hx\n", __func__, + radio->registers[14], radio->registers[15]); + +done: + pr_info("%s exit %d\n", __func__, retval); + mutex_unlock(&radio->lock); + return retval; +} + +/************************************************************************** + * File Operations Interface + **************************************************************************/ + +/* + * rtc6226_fops_read - read RDS data + */ +static ssize_t rtc6226_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + unsigned int block_count = 0; + + /* switch on rds reception */ + mutex_lock(&radio->lock); + /* if RDS is not on, then turn on RDS */ + if ((radio->registers[SYSCFG] & SYSCFG_CSR0_RDS_EN) == 0) + rtc6226_rds_on(radio); + + /* block if no new data available */ + while (radio->wr_index == radio->rd_index) { + if (file->f_flags & O_NONBLOCK) { + retval = -EWOULDBLOCK; + goto done; + } + if (wait_event_interruptible(radio->read_queue, + radio->wr_index != radio->rd_index) < 0) { + retval = -EINTR; + goto done; + } + } + + /* calculate block count from byte count */ + count /= 3; + #ifdef _RDSDEBUG + pr_info("%s : count = %zu\n", __func__, count); + #endif + + /* copy RDS block out of internal buffer and to user buffer */ + while (block_count < count) { + if (radio->rd_index == radio->wr_index) + break; + /* always transfer rds complete blocks */ + if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3)) + /* retval = -EFAULT; */ + break; + /* increment and wrap read pointer */ + radio->rd_index += 3; + if (radio->rd_index >= radio->buf_size) + radio->rd_index = 0; + /* increment counters */ + block_count++; + buf += 3; + retval += 3; + #ifdef _RDSDEBUG + pr_info("%s : block_count = %d, count = %zu\n", __func__, + block_count, count); + #endif + } + +done: + mutex_unlock(&radio->lock); + return retval; +} + +/* + * rtc6226_fops_poll - poll RDS data + */ +static unsigned int rtc6226_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + /* switch on rds reception */ + mutex_lock(&radio->lock); + if ((radio->registers[SYSCFG] & SYSCFG_CSR0_RDS_EN) == 0) + rtc6226_rds_on(radio); + mutex_unlock(&radio->lock); + + poll_wait(file, &radio->read_queue, pts); + + if (radio->rd_index != radio->wr_index) + retval = POLLIN | POLLRDNORM; + + return retval; +} + +/* static */ +int rtc6226_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + pr_info("%s enter, ctrl->id: %x, value:%d\n", __func__, + ctrl->id, ctrl->value); + + mutex_lock(&radio->lock); + + switch (ctrl->id) { + case V4L2_CID_PRIVATE_CSR0_ENABLE: + pr_info("V4L2_CID_PRIVATE_CSR0_ENABLE val=%d\n", ctrl->value); + break; + case V4L2_CID_PRIVATE_CSR0_DISABLE: + pr_info("V4L2_CID_PRIVATE_CSR0_DISABLE val=%d\n", ctrl->value); + break; + case V4L2_CID_PRIVATE_CSR0_VOLUME: + case V4L2_CID_AUDIO_VOLUME: + ctrl->value = radio->registers[MPXCFG] & MPXCFG_CSR0_VOLUME; + break; + case V4L2_CID_PRIVATE_CSR0_DIS_MUTE: + case V4L2_CID_AUDIO_MUTE: + ctrl->value = ((radio->registers[MPXCFG] & + MPXCFG_CSR0_DIS_MUTE) == 0) ? 1 : 0; + break; + case V4L2_CID_PRIVATE_CSR0_DIS_SMUTE: + ctrl->value = ((radio->registers[MPXCFG] & + MPXCFG_CSR0_DIS_SMUTE) == 0) ? 1 : 0; + break; + case V4L2_CID_PRIVATE_CSR0_BAND: + ctrl->value = radio->band; + break; + case V4L2_CID_PRIVATE_CSR0_SEEKRSSITH: + ctrl->value = radio->registers[SEEKCFG1] & + SEEKCFG1_CSR0_RSSI_LOW_TH; + break; + case V4L2_CID_PRIVATE_RSSI: + rtc6226_get_all_registers(radio); + ctrl->value = radio->registers[RSSI] & RSSI_RSSI; + pr_info("Get V4L2_CONTROL V4L2_CID_PRIVATE_RSSI: STATUS=0x%4.4hx RSSI = %d\n", + radio->registers[STATUS], + radio->registers[RSSI] & RSSI_RSSI); + pr_info("Get V4L2_CONTROL V4L2_CID_PRIVATE_RSSI: regC=0x%4.4hx RegD=0x%4.4hx\n", + radio->registers[BA_DATA], radio->registers[BB_DATA]); + pr_info("Get V4L2_CONTROL V4L2_CID_PRIVATE_RSSI: regE=0x%4.4hx RegF=0x%4.4hx\n", + radio->registers[BC_DATA], radio->registers[BD_DATA]); + break; + case V4L2_CID_PRIVATE_DEVICEID: + ctrl->value = radio->registers[DEVICEID] & DEVICE_ID; + pr_info("Get V4L2_CONTROL V4L2_CID_PRIVATE_DEVICEID: DEVICEID=0x%4.4hx\n", + radio->registers[DEVICEID]); + break; + default: + pr_info("%s in default id:%d\n", __func__, ctrl->id); + retval = -EINVAL; + } + + mutex_unlock(&radio->lock); + return retval; +} + +static int rtc6226_vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buffer) +{ + + struct rtc6226_device *radio = video_get_drvdata(video_devdata(file)); + enum rtc6226_buf_t buf_type = -1; + u8 buf_fifo[STD_BUF_SIZE] = {0}; + struct kfifo *data_fifo = NULL; + u8 *buf = NULL; + int len = 0, retval = -1; + + if ((radio == NULL) || (buffer == NULL)) { + pr_err("%s radio/buffer is NULL\n", __func__); + return -ENXIO; + } + + buf_type = buffer->index; + buf = (u8 *)buffer->m.userptr; + len = buffer->length; + pr_info("%s: requesting buffer %d\n", __func__, buf_type); + + if ((buf_type < RTC6226_FM_BUF_MAX) && (buf_type >= 0)) { + data_fifo = &radio->data_buf[buf_type]; + if (buf_type == RTC6226_FM_BUF_EVENTS) { + if (wait_event_interruptible(radio->event_queue, + kfifo_len(data_fifo)) < 0) { + return -EINTR; + } + } + } else { + pr_err("%s invalid buffer type\n", __func__); + return -EINVAL; + } + if (len <= STD_BUF_SIZE) { + buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0], + len, &radio->buf_lock[buf_type]); + } else { + pr_err("%s kfifo_out_locked can not use len more than 128\n", + __func__); + return -EINVAL; + } + retval = copy_to_user(buf, &buf_fifo[0], buffer->bytesused); + if (retval > 0) { + pr_err("%s Failed to copy %d bytes data\n", __func__, retval); + return -EAGAIN; + } + + return retval; +} + +static bool check_mode(struct rtc6226_device *radio) +{ + bool retval = true; + + if (radio->mode == FM_OFF || radio->mode == FM_RECV) + retval = false; + + return retval; +} + + + +static int rtc6226_disable(struct rtc6226_device *radio) +{ + int retval = 0; + + /* disable RDS/STC interrupt */ + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN; + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN; + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_STDIRQEN; + retval = rtc6226_set_register(radio, SYSCFG); + if (retval < 0) { + pr_err("%s fail to disable RDS/SCT interrupt\n", __func__); + goto done; + } + retval = rtc6226_power_down(radio); + if (retval < 0) { + pr_err("%s fail to turn off fmradio\n", __func__); + goto done; + } + + if (radio->mode == FM_TURNING_OFF || radio->mode == FM_RECV) { + pr_info("%s: posting RTC6226_EVT_RADIO_DISABLED event\n", + __func__); + rtc6226_q_event(radio, RTC6226_EVT_RADIO_DISABLED); + radio->mode = FM_OFF; + } + /* flush_workqueue(radio->wqueue); */ + +done: + return retval; +} + +static int rtc6226_enable(struct rtc6226_device *radio) +{ + int retval = 0; + + retval = rtc6226_get_register(radio, POWERCFG); + if ((radio->registers[POWERCFG] & POWERCFG_CSR0_ENABLE) == 0) + rtc6226_power_up(radio); + else + pr_info("%s already turn on\n", __func__); + + if ((radio->registers[SYSCFG] & SYSCFG_CSR0_STDIRQEN) == 0) { + radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN; + radio->registers[SYSCFG] |= SYSCFG_CSR0_STDIRQEN; + retval = rtc6226_set_register(radio, SYSCFG); + if (retval < 0) { + pr_err("%s set register fail\n", __func__); + goto done; + } else { + rtc6226_q_event(radio, RTC6226_EVT_RADIO_READY); + radio->mode = FM_RECV; + } + } else { + rtc6226_q_event(radio, RTC6226_EVT_RADIO_READY); + radio->mode = FM_RECV; + } +done: + return retval; + +} + +bool rtc6226_is_valid_srch_mode(int srch_mode) +{ + if ((srch_mode >= RTC6226_MIN_SRCH_MODE) && + (srch_mode <= RTC6226_MAX_SRCH_MODE)) + return true; + else + return false; +} + +/* + * rtc6226_vidioc_s_ctrl - set the value of a control + */ +int rtc6226_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + int space_s = 0; + + pr_info("%s enter, ctrl->id: %x, value:%d\n", __func__, + ctrl->id, ctrl->value); + + switch (ctrl->id) { + case V4L2_CID_PRIVATE_RTC6226_STATE: + if (ctrl->value == FM_RECV) { + if (check_mode(radio)) { + pr_err("%s:fm is not in proper state\n", + __func__); + retval = -EINVAL; + goto end; + } + radio->mode = FM_RECV_TURNING_ON; + retval = rtc6226_enable(radio); + if (retval < 0) { + pr_err( + "%s Error while enabling RECV FM %d\n", + __func__, retval); + radio->mode = FM_OFF; + goto end; + } + } else if (ctrl->value == FM_OFF) { + radio->mode = FM_TURNING_OFF; + retval = rtc6226_disable(radio); + if (retval < 0) { + pr_err("Err on disable recv FM %d\n", retval); + radio->mode = FM_RECV; + goto end; + } + } + break; + case V4L2_CID_PRIVATE_RTC6226_SET_AUDIO_PATH: + case V4L2_CID_PRIVATE_RTC6226_SRCH_ALGORITHM: + case V4L2_CID_PRIVATE_RTC6226_REGION: + retval = 0; + break; + case V4L2_CID_PRIVATE_RTC6226_EMPHASIS: + if (ctrl->value == 1) + radio->registers[MPXCFG] |= MPXCFG_CSR0_DEEM; + else + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DEEM; + retval = rtc6226_set_register(radio, MPXCFG); + break; + case V4L2_CID_PRIVATE_RTC6226_RDS_STD: + /* enable RDS / STC interrupt */ + radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN; + radio->registers[SYSCFG] |= SYSCFG_CSR0_STDIRQEN; + /*radio->registers[SYSCFG] |= SYSCFG_CSR0_RDS_EN;*/ + retval = rtc6226_set_register(radio, SYSCFG); + break; + case V4L2_CID_PRIVATE_RTC6226_SPACING: + space_s = ctrl->value; + radio->space = ctrl->value; + radio->registers[CHANNEL] &= ~CHANNEL_CSR0_CHSPACE; + radio->registers[CHANNEL] |= (space_s << 10); + retval = rtc6226_set_register(radio, CHANNEL); + break; + case V4L2_CID_PRIVATE_RTC6226_SRCHON: + rtc6226_search(radio, (bool)ctrl->value); + break; + case V4L2_CID_PRIVATE_RTC6226_LP_MODE: + case V4L2_CID_PRIVATE_RTC6226_ANTENNA: + case V4L2_CID_PRIVATE_RTC6226_RDSON: + case V4L2_CID_PRIVATE_RTC6226_AF_JUMP: + case V4L2_CID_PRIVATE_RTC6226_SRCH_CNT: + case V4L2_CID_PRIVATE_RTC6226_RXREPEATCOUNT: + retval = 0; + break; + case V4L2_CID_PRIVATE_RTC6226_RSSI_TH: + case V4L2_CID_PRIVATE_RTC6226_SINR_THRESHOLD: + radio->rssi_th = ctrl->value >> 16; + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_RSSI_LOW_TH; + radio->registers[SEEKCFG1] |= radio->rssi_th; + retval = rtc6226_set_register(radio, SEEKCFG1); + if (retval < 0) + pr_err("%s fail to set rssi\n", __func__); + rtc6226_get_register(radio, SEEKCFG1); + pr_info("FMRICHWAVE RSSI_TH: Dec = %d , Hexa = %x\n", + radio->registers[SEEKCFG1] & 0xFF, + radio->registers[SEEKCFG1] & 0xFF); + break; + /* case V4L2_CID_PRIVATE_RTC6226_OFS_THRESHOLD: */ + case V4L2_CID_PRIVATE_RTC6226_SPUR_FREQ_RMSSI: + break; + case V4L2_CID_PRIVATE_RTC6226_RDSGROUP_MASK: + case V4L2_CID_PRIVATE_RTC6226_RDSGROUP_PROC: + if ((radio->registers[SYSCFG] & SYSCFG_CSR0_RDS_EN) == 0) + rtc6226_rds_on(radio); + retval = 0; + break; + case V4L2_CID_PRIVATE_RTC6226_SRCHMODE: + if (rtc6226_is_valid_srch_mode(ctrl->value)) { + radio->g_search_mode = ctrl->value; + } else { + pr_err("%s:srch mode is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_RTC6226_PSALL: + break; + case V4L2_CID_PRIVATE_RTC6226_SCANDWELL: + if ((ctrl->value >= MIN_DWELL_TIME) && + (ctrl->value <= MAX_DWELL_TIME)) { + radio->dwell_time_sec = ctrl->value; + } else { + pr_err( + "%s:scandwell period is not valid\n", __func__); + retval = -EINVAL; + } + break; + case V4L2_CID_PRIVATE_CSR0_ENABLE: + pr_info("V4L2_CID_PRIVATE_CSR0_ENABLE val=%d\n", + ctrl->value); + retval = rtc6226_power_up(radio); + /* must keep below line */ + ctrl->value = 0; + break; + case V4L2_CID_PRIVATE_CSR0_DISABLE: + pr_info("V4L2_CID_PRIVATE_CSR0_DISABLE val=%d\n", + ctrl->value); + retval = rtc6226_power_down(radio); + /* must keep below line */ + ctrl->value = 0; + break; + case V4L2_CID_PRIVATE_DEVICEID: + pr_info("V4L2_CID_PRIVATE_DEVICEID val=%d\n", ctrl->value); + break; + case V4L2_CID_PRIVATE_CSR0_VOLUME: + case V4L2_CID_AUDIO_VOLUME: + pr_info("MPXCFG=0x%4.4hx POWERCFG=0x%4.4hx\n", + radio->registers[MPXCFG], radio->registers[POWERCFG]); + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_VOLUME; + radio->registers[MPXCFG] |= + (ctrl->value > 15) ? 8 : ctrl->value; + pr_info("MPXCFG=0x%4.4hx POWERCFG=0x%4.4hx\n", + radio->registers[MPXCFG], radio->registers[POWERCFG]); + retval = rtc6226_set_register(radio, MPXCFG); + break; + case V4L2_CID_PRIVATE_CSR0_DIS_MUTE: + case V4L2_CID_AUDIO_MUTE: + if (ctrl->value == 1) + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DIS_MUTE; + else + radio->registers[MPXCFG] |= MPXCFG_CSR0_DIS_MUTE; + retval = rtc6226_set_register(radio, MPXCFG); + break; + case V4L2_CID_PRIVATE_CSR0_DIS_SMUTE: + pr_info("V4L2_CID_PRIVATE_CSR0_DIS_SMUTE\n"); + if (ctrl->value == 1) + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DIS_SMUTE; + else + radio->registers[MPXCFG] |= MPXCFG_CSR0_DIS_SMUTE; + retval = rtc6226_set_register(radio, MPXCFG); + break; + case V4L2_CID_PRIVATE_CSR0_DEEM: + pr_info("V4L2_CID_PRIVATE_CSR0_DEEM\n"); + if (ctrl->value == 1) + radio->registers[MPXCFG] |= MPXCFG_CSR0_DEEM; + else + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DEEM; + retval = rtc6226_set_register(radio, MPXCFG); + break; + case V4L2_CID_PRIVATE_CSR0_BLNDADJUST: + pr_info("V4L2_CID_PRIVATE_CSR0_BLNDADJUST val=%d\n", + ctrl->value); + break; + case V4L2_CID_PRIVATE_CSR0_BAND: + pr_info( + "V4L2_CID_PRIVATE_CSR0_BAND : FREQ_TOP=%d FREQ_BOT=%d %d\n", + radio->registers[RADIOSEEKCFG1], + radio->registers[RADIOSEEKCFG2], ctrl->value); + switch (ctrl->value) { + case FMBAND_87_108_MHZ: + radio->registers[RADIOSEEKCFG1] = 10800; + radio->registers[RADIOSEEKCFG2] = 8750; + break; + case FMBAND_76_108_MHZ: + radio->registers[RADIOSEEKCFG1] = 10800; + radio->registers[RADIOSEEKCFG2] = 7600; + break; + case FMBAND_76_91_MHZ: + radio->registers[RADIOSEEKCFG1] = 9100; + radio->registers[RADIOSEEKCFG2] = 7600; + break; + case FMBAND_64_76_MHZ: + radio->registers[RADIOSEEKCFG1] = 7600; + radio->registers[RADIOSEEKCFG2] = 6400; + break; + default: + retval = -EINVAL; + break; + } + pr_info( + "V4L2_CID_PRIVATE_CSR0_BAND : FREQ_TOP=%d FREQ_BOT=%d %d\n", + radio->registers[RADIOSEEKCFG1], + radio->registers[RADIOSEEKCFG2], ctrl->value); + radio->band = ctrl->value; + retval = rtc6226_set_register(radio, RADIOSEEKCFG1); + retval = rtc6226_set_register(radio, RADIOSEEKCFG2); + break; + case V4L2_CID_PRIVATE_CSR0_CHSPACE: + pr_info("V4L2_CID_PRIVATE_CSR0_CHSPACE : FM_SPACE=%d %d\n", + radio->registers[RADIOCFG], ctrl->value); + switch (ctrl->value) { + case FMSPACE_200_KHZ: + radio->registers[RADIOCFG] = 20; + break; + case FMSPACE_100_KHZ: + radio->registers[RADIOCFG] = 10; + break; + case FMSPACE_50_KHZ: + radio->registers[RADIOCFG] = 5; + break; + default: + retval = -EINVAL; + break; + } + radio->space = ctrl->value; + pr_info("V4L2_CID_PRIVATE_CSR0_CHSPACE : FM_SPACE=%d %d\n", + radio->registers[RADIOCFG], ctrl->value); + retval = rtc6226_set_register(radio, RADIOCFG); + break; + case V4L2_CID_PRIVATE_CSR0_DIS_AGC: + pr_info("V4L2_CID_PRIVATE_CSR0_DIS_AGC val=%d\n", + ctrl->value); + break; + case V4L2_CID_PRIVATE_CSR0_RDS_EN: + pr_info( + "V4L2_CSR0_RDS_EN:CHANNEL=0x%4.4hx SYSCFG=0x%4.4hx\n", + radio->registers[CHANNEL], + radio->registers[SYSCFG]); + rtc6226_reset_rds_data(radio); + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN; + radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN; + radio->registers[SYSCFG] |= (ctrl->value << 15); + radio->registers[SYSCFG] |= (ctrl->value << 12); + pr_info + ("V4L2_CSR0_RDS_EN : CHANNEL=0x%4.4hx SYSCFG=0x%4.4hx\n", + radio->registers[CHANNEL], + radio->registers[SYSCFG]); + retval = rtc6226_set_register(radio, SYSCFG); + break; + case V4L2_CID_PRIVATE_SEEK_CANCEL: + rtc6226_search(radio, (bool)ctrl->value); + break; + case V4L2_CID_PRIVATE_CSR0_SEEKRSSITH: + radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_RSSI_LOW_TH; + radio->registers[SEEKCFG1] |= ctrl->value; + retval = rtc6226_set_register(radio, SEEKCFG1); + break; + default: + pr_info("%s id: %x in default\n", __func__, ctrl->id); + retval = -EINVAL; + break; + } + +end: + pr_info("%s exit id: %x , ret: %d\n", __func__, ctrl->id, retval); + + return retval; +} + +/* + * rtc6226_vidioc_g_audio - get audio attributes + */ +static int rtc6226_vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *audio) +{ + /* driver constants */ + audio->index = 0; + strlcpy(audio->name, "Radio", sizeof(audio->name)); + audio->capability = V4L2_AUDCAP_STEREO; + audio->mode = 0; + + return 0; +} + + +/* + * rtc6226_vidioc_g_tuner - get tuner attributes + */ +static int rtc6226_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + pr_info("%s enter\n", __func__); + + if (tuner->index != 0) { + retval = -EINVAL; + goto done; + } + + retval = rtc6226_get_register(radio, RSSI); + if (retval < 0) + goto done; + + /* driver constants */ + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO; + + tuner->rangelow = (radio->registers[RADIOSEEKCFG1] & + CHANNEL_CSR0_FREQ_TOP) * 10; + tuner->rangehigh = (radio->registers[RADIOSEEKCFG2] & + CHANNEL_CSR0_FREQ_BOT) * 10; + + /* stereo indicator == stereo (instead of mono) */ + if ((radio->registers[STATUS] & STATUS_SI) == 0) + tuner->rxsubchans = V4L2_TUNER_SUB_MONO; + else + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + /* If there is a reliable method of detecting an RDS channel, + * then this code should check for that before setting this + * RDS subchannel. + */ + tuner->rxsubchans |= V4L2_TUNER_SUB_RDS; + + /* mono/stereo selector */ + if ((radio->registers[MPXCFG] & MPXCFG_CSR0_MONO) == 0) + tuner->audmode = V4L2_TUNER_MODE_STEREO; + else + tuner->audmode = V4L2_TUNER_MODE_MONO; + + /* min is worst, max is best; rssi: 0..0xff */ + tuner->signal = (radio->registers[RSSI] & RSSI_RSSI); + +done: + pr_info("%s exit %d\n", __func__, retval); + + return retval; +} + + +/* + * rtc6226_vidioc_s_tuner - set tuner attributes + */ +static int rtc6226_vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + pr_info("%s entry\n", __func__); + + if (tuner->index != 0) { + pr_info("%s index :%d\n", __func__, tuner->index); + goto done; + } + + /* mono/stereo selector */ + switch (tuner->audmode) { + case V4L2_TUNER_MODE_MONO: + radio->registers[MPXCFG] |= MPXCFG_CSR0_MONO; /* force mono */ + break; + case V4L2_TUNER_MODE_STEREO: + radio->registers[MPXCFG] &= ~MPXCFG_CSR0_MONO; /* try stereo */ + break; + default: + goto done; + } + + retval = rtc6226_set_register(radio, MPXCFG); + + pr_info("%s low:%d high:%d\n", __func__, + tuner->rangelow, tuner->rangehigh); + + /* set band */ + if (tuner->rangelow || tuner->rangehigh) { + for (band = 0; band < ARRAY_SIZE(bands); band++) { + if (bands[band].rangelow == tuner->rangelow && + bands[band].rangehigh == tuner->rangehigh) + break; + } + if (band == ARRAY_SIZE(bands)) { + pr_err("%s err\n", __func__); + band = 0; + } + } else + band = 0; /* If nothing is specified seek 87.5 - 108 Mhz */ + + if (radio->band != band) { + radio->registers[CHANNEL] |= (band << 12); + rtc6226_set_register(radio, MPXCFG); + radio->band = band; + } +done: + pr_info("%s exit %d\n", __func__, retval); + return retval; +} + + +/* + * rtc6226_vidioc_g_frequency - get tuner or modulator radio frequency + */ +static int rtc6226_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + pr_info("%s enter freq %d\n", __func__, freq->frequency); + + freq->type = V4L2_TUNER_RADIO; + retval = rtc6226_get_freq(radio, &freq->frequency); + pr_info(" %s *freq=%d, ret %d\n", __func__, freq->frequency, retval); + + if (retval < 0) + pr_err(" %s get frequency failed with %d\n", __func__, retval); + + return retval; +} + + +/* + * rtc6226_vidioc_s_frequency - set tuner or modulator radio frequency + */ +static int rtc6226_vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *freq) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + pr_info("%s enter freq = %d\n", __func__, freq->frequency); + + retval = rtc6226_set_freq(radio, freq->frequency); + + if (retval < 0) + pr_err("%s set frequency failed with %d\n", __func__, retval); + + return retval; +} + + +/* + * rtc6226_vidioc_s_hw_freq_seek - set hardware frequency seek + */ +static int rtc6226_vidioc_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = 0; + + pr_info("%s enter\n", __func__); + + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + radio->is_search_cancelled = false; + + if (radio->g_search_mode == SEEK) { + /* seek */ + pr_info("%s starting seek\n", __func__); + radio->seek_tune_status = SEEK_PENDING; + retval = rtc6226_set_seek(radio, seek->seek_upward, + WRAP_ENABLE); + rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC); + radio->seek_tune_status = NO_SEEK_TUNE_PENDING; + rtc6226_q_event(radio, RTC6226_EVT_SCAN_NEXT); + } else if ((radio->g_search_mode == SCAN) || + (radio->g_search_mode == SCAN_FOR_STRONG)) { + /* scan */ + if (radio->g_search_mode == SCAN_FOR_STRONG) { + pr_info("%s starting search list\n", __func__); + memset(&radio->srch_list, 0, + sizeof(struct rtc6226_srch_list_compl)); + } else { + pr_info("%s starting scan\n", __func__); + } + rtc6226_search(radio, 1); + } else { + retval = -EINVAL; + pr_err("In %s, invalid search mode %d\n", + __func__, radio->g_search_mode); + } + pr_info("%s exit %d\n", __func__, retval); + return retval; +} + +#ifndef RW_Kernel_ENG +static int rtc6226_vidioc_g_fmt_type_private(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; +} +#endif + +static const struct v4l2_file_operations rtc6226_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = v4l2_compat_ioctl32, +#endif + .read = rtc6226_fops_read, + .poll = rtc6226_fops_poll, + .open = rtc6226_fops_open, + .release = rtc6226_fops_release, +}; + +/* + * rtc6226_ioctl_ops - video device ioctl operations + */ +/* static */ +const struct v4l2_ioctl_ops rtc6226_ioctl_ops = { + .vidioc_querycap = rtc6226_vidioc_querycap, + .vidioc_g_audio = rtc6226_vidioc_g_audio, + .vidioc_g_tuner = rtc6226_vidioc_g_tuner, + .vidioc_s_tuner = rtc6226_vidioc_s_tuner, + .vidioc_g_ctrl = rtc6226_vidioc_g_ctrl, + .vidioc_s_ctrl = rtc6226_vidioc_s_ctrl, + .vidioc_g_frequency = rtc6226_vidioc_g_frequency, + .vidioc_s_frequency = rtc6226_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = rtc6226_vidioc_s_hw_freq_seek, + .vidioc_dqbuf = rtc6226_vidioc_dqbuf, +#ifndef RW_Kernel_ENG + .vidioc_g_fmt_type_private = rtc6226_vidioc_g_fmt_type_private, +#endif +}; + +/* + * rtc6226_viddev_template - video device interface + */ +struct video_device rtc6226_viddev_template = { + .fops = &rtc6226_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, + .ioctl_ops = &rtc6226_ioctl_ops, +}; + +/************************************************************************** + * Module Interface + **************************************************************************/ + +/* + * rtc6226_i2c_init - module init + */ +static __init int rtc6226_init(void) +{ + pr_info(DRIVER_DESC ", Version " DRIVER_VERSION "\n"); + return rtc6226_i2c_init(); +} + +/* + * rtc6226_i2c_exit - module exit + */ +static void __exit rtc6226_exit(void) +{ + i2c_del_driver(&rtc6226_i2c_driver); +} + +module_init(rtc6226_init); +module_exit(rtc6226_exit); diff --git a/drivers/media/radio/rtc6226/radio-rtc6226-i2c.c b/drivers/media/radio/rtc6226/radio-rtc6226-i2c.c new file mode 100644 index 000000000000..eb7fb4963b1b --- /dev/null +++ b/drivers/media/radio/rtc6226/radio-rtc6226-i2c.c @@ -0,0 +1,739 @@ +/* drivers/media/radio/rtc6226/radio-rtc6226-i2c.c + * + * Driver for Richwave RTC6226 FM Tuner + * + * Copyright (c) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * Copyright (c) 2009 Tobias Lorenz + * Copyright (c) 2012 Hans de Goede + * Copyright (c) 2018 LG Electronics, Inc. + * Copyright (c) 2018 Richwave Technology Co.Ltd + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "radio-rtc6226.h" +#include + +static const struct of_device_id rtc6226_i2c_dt_ids[] = { + {.compatible = "rtc6226"}, + {} +}; + +/* I2C Device ID List */ +static const struct i2c_device_id rtc6226_i2c_id[] = { + /* Generic Entry */ + { "rtc6226", 0 }, + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(i2c, rtc6226_i2c_id); + + +/************************************************************************** + * Module Parameters + **************************************************************************/ + +/* Radio Nr */ +static int radio_nr = -1; +MODULE_PARM_DESC(radio_nr, "Radio Nr"); + +/* RDS buffer blocks */ +static unsigned int rds_buf = 100; +MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*"); + +enum rtc6226_ctrl_id { + RTC6226_ID_CSR0_ENABLE, + RTC6226_ID_CSR0_DISABLE, + RTC6226_ID_DEVICEID, + RTC6226_ID_CSR0_DIS_SMUTE, + RTC6226_ID_CSR0_DIS_MUTE, + RTC6226_ID_CSR0_DEEM, + RTC6226_ID_CSR0_BLNDADJUST, + RTC6226_ID_CSR0_VOLUME, + RTC6226_ID_CSR0_BAND, + RTC6226_ID_CSR0_CHSPACE, + RTC6226_ID_CSR0_DIS_AGC, + RTC6226_ID_CSR0_RDS_EN, + RTC6226_ID_SEEK_CANCEL, + RTC6226_ID_CSR0_SEEKRSSITH, + RTC6226_ID_CSR0_OFSTH, + RTC6226_ID_CSR0_QLTTH, + RTC6226_ID_RSSI, + RTC6226_ID_RDS_RDY, + RTC6226_ID_STD, + RTC6226_ID_SF, + RTC6226_ID_RDS_SYNC, + RTC6226_ID_SI, +}; + + +/************************************************************************** + * I2C Definitions + **************************************************************************/ +/* Write starts with the upper byte of register 0x02 */ +#define WRITE_REG_NUM 3 +#define WRITE_INDEX(i) ((i + 0x02)%16) + +/* Read starts with the upper byte of register 0x0a */ +#define READ_REG_NUM 2 +#define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM) + +/*static*/ +struct tasklet_struct my_tasklet; +/* + * rtc6226_get_register - read register + */ +int rtc6226_get_register(struct rtc6226_device *radio, int regnr) +{ + u8 reg[1]; + u8 buf[READ_REG_NUM]; + struct i2c_msg msgs[2] = { + { radio->client->addr, 0, 1, reg }, + { radio->client->addr, I2C_M_RD, sizeof(buf), buf }, + }; + + reg[0] = (u8)(regnr); + if (i2c_transfer(radio->client->adapter, msgs, 2) != 2) + return -EIO; + + radio->registers[regnr] = + (u16)(((buf[0] << 8) & 0xff00) | buf[1]); + + return 0; +} + +/* + * rtc6226_set_register - write register + */ +int rtc6226_set_register(struct rtc6226_device *radio, int regnr) +{ + u8 buf[WRITE_REG_NUM]; + struct i2c_msg msgs[1] = { + { radio->client->addr, 0, sizeof(u8) * WRITE_REG_NUM, + (void *)buf }, + }; + + buf[0] = (u8)(regnr); + buf[1] = (u8)((radio->registers[(u8)(regnr) & 0xFF] >> 8) & 0xFF); + buf[2] = (u8)(radio->registers[(u8)(regnr) & 0xFF] & 0xFF); + + if (i2c_transfer(radio->client->adapter, msgs, 1) != 1) + return -EIO; + + return 0; +} + +/* + * rtc6226_set_register - write register + */ +int rtc6226_set_serial_registers(struct rtc6226_device *radio, + u16 *data, int regnr) +{ + u8 buf[WRITE_REG_NUM]; + struct i2c_msg msgs[1] = { + { radio->client->addr, 0, sizeof(u8) * WRITE_REG_NUM, + (void *)buf }, + }; + + buf[0] = (u8)(regnr); + buf[1] = (u8)((data[0] >> 8) & 0xFF); + buf[2] = (u8)(data[0] & 0xFF); + + if (i2c_transfer(radio->client->adapter, msgs, 1) != 1) + return -EIO; + + return 0; +} + +/************************************************************************** + * General Driver Functions - ENTIRE REGISTERS + **************************************************************************/ +/* + * rtc6226_get_all_registers - read entire registers + */ +/* changed from static */ +int rtc6226_get_all_registers(struct rtc6226_device *radio) +{ + int i; + int err; + u8 reg[1] = {0x00}; + u8 buf[RADIO_REGISTER_NUM * 2]; + struct i2c_msg msgs1[1] = { + { radio->client->addr, 0, 1, reg}, + }; + struct i2c_msg msgs[1] = { + { radio->client->addr, I2C_M_RD, sizeof(buf), buf }, + }; + + if (i2c_transfer(radio->client->adapter, msgs1, 1) != 1) + return -EIO; + + err = i2c_transfer(radio->client->adapter, msgs, 1); + + if (err < 0) + return -EIO; + + for (i = 0; i < 16; i++) + radio->registers[i] = + (u16)(((buf[i*2] << 8) & 0xff00) | buf[i*2+1]); + + return 0; +} + +/* + * rtc6226_vidioc_querycap - query device capabilities + */ +int rtc6226_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + pr_info("%s enter\n", __func__); + strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | + V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE; + capability->capabilities = capability->device_caps | + V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +/* + * rtc6226_i2c_interrupt - interrupt handler + */ +static void rtc6226_i2c_interrupt_handler(struct rtc6226_device *radio) +{ + unsigned char regnr; + int retval = 0; + + pr_info("%s enter\n", __func__); + + /* check Seek/Tune Complete */ + retval = rtc6226_get_register(radio, STATUS); + if (retval < 0) { + pr_err("%s read fail to STATUS\n", __func__); + goto end; + } + retval = rtc6226_get_register(radio, RSSI); + if (retval < 0) { + pr_err("%s read fail to RSSI\n", __func__); + goto end; + } + + if (radio->registers[STATUS] & STATUS_STD) { + complete(&radio->completion); + pr_info("%s Seek/Tune Done\n", __func__); + } + + /* Update RDS registers */ + for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) { + retval = rtc6226_get_register(radio, STATUS + regnr); + if (retval < 0) + goto end; + } + + /* get rds blocks */ + if ((radio->registers[STATUS] & STATUS_RDS_RDY) == 0) { + /* No RDS group ready, better luck next time */ + pr_err("%s No RDS group ready\n", __func__); + goto end; + } else { + /* avoid RDS interrupt lock disable_irq*/ + if ((radio->registers[SYSCFG] & SYSCFG_CSR0_RDS_EN) != 0) { + pr_info("%s start rds handler\n", __func__); + schedule_work(&radio->rds_worker); + } + } +end: + pr_info("%s exit :%d\n", __func__, retval); +} + +static irqreturn_t rtc6226_isr(int irq, void *dev_id) +{ + struct rtc6226_device *radio = dev_id; + /* + * The call to queue_delayed_work ensures that a minimum delay + * (in jiffies) passes before the work is actually executed. The return + * value from the function is nonzero if the work_struct was actually + * added to queue (otherwise, it may have already been there and will + * not be added a second time). + */ + + queue_delayed_work(radio->wqueue, &radio->work, + msecs_to_jiffies(10)); + + return IRQ_HANDLED; +} + +static void rtc6226_handler(struct work_struct *work) +{ + struct rtc6226_device *radio; + + radio = container_of(work, struct rtc6226_device, work.work); + + rtc6226_i2c_interrupt_handler(radio); +} + +void rtc6226_disable_irq(struct rtc6226_device *radio) +{ + int irq; + + irq = radio->irq; + disable_irq_wake(irq); + free_irq(irq, radio); + + cancel_delayed_work_sync(&radio->work); + flush_workqueue(radio->wqueue); + + cancel_work_sync(&radio->rds_worker); + flush_workqueue(radio->wqueue_rds); + cancel_delayed_work_sync(&radio->work_scan); + flush_workqueue(radio->wqueue_scan); +} + +int rtc6226_enable_irq(struct rtc6226_device *radio) +{ + int retval; + int irq; + + retval = gpio_direction_input(radio->int_gpio); + if (retval) { + pr_err("%s unable to set the gpio %d direction(%d)\n", + __func__, radio->int_gpio, retval); + return retval; + } + radio->irq = gpio_to_irq(radio->int_gpio); + irq = radio->irq; + + if (radio->irq < 0) { + pr_err("%s: gpio_to_irq returned %d\n", __func__, radio->irq); + goto open_err_req_irq; + } + + pr_info("%s irq number is = %d\n", __func__, radio->irq); + + retval = request_any_context_irq(radio->irq, rtc6226_isr, + IRQF_TRIGGER_FALLING, DRIVER_NAME, radio); + + if (retval < 0) { + pr_err("%s Couldn't acquire FM gpio %d, retval:%d\n", + __func__, radio->irq, retval); + goto open_err_req_irq; + } else { + pr_info("%s FM GPIO %d registered\n", __func__, radio->irq); + } + retval = enable_irq_wake(irq); + if (retval < 0) { + pr_err("Could not wake FM interrupt\n"); + free_irq(irq, radio); + } + return retval; + +open_err_req_irq: + rtc6226_disable_irq(radio); + + return retval; +} + +/* + * rtc6226_fops_open - file open + */ +int rtc6226_fops_open(struct file *file) +{ + struct rtc6226_device *radio = video_drvdata(file); + int retval = v4l2_fh_open(file); + int i; + + pr_info("%s enter\n", __func__); + if (retval) { + pr_err("%s fail to open v4l2\n", __func__); + return retval; + } + + INIT_DELAYED_WORK(&radio->work, rtc6226_handler); + INIT_DELAYED_WORK(&radio->work_scan, rtc6226_scan); + INIT_WORK(&radio->rds_worker, rtc6226_rds_handler); + + if (v4l2_fh_is_singular_file(file)) { + retval = rtc6226_power_up(radio); + if (retval < 0) { + pr_err("%s fail to power_up\n", __func__); + goto done; + //return retval; + } + } + + retval = rtc6226_get_all_registers(radio); + if (retval < 0) + pr_err("%s fail to get register %d\n", __func__, retval); + else { + for (i = 0; i < 16; i++) + pr_info("%s registers[%d]:%x\n", __func__, i, + radio->registers[i]); + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + rtc6226_enable_irq(radio); + +done: + if (retval) + v4l2_fh_release(file); + return retval; +} + +/* + * rtc6226_fops_release - file release + */ +int rtc6226_fops_release(struct file *file) +{ + struct rtc6226_device *radio = video_drvdata(file); + + pr_info("%s : Exit\n", __func__); + if (v4l2_fh_is_singular_file(file)) { + if (radio->mode != FM_OFF) { + rtc6226_power_down(radio); + radio->mode = FM_OFF; + } + } + rtc6226_disable_irq(radio); + return v4l2_fh_release(file); +} + +static int rtc6226_parse_dt(struct device *dev, + struct rtc6226_device *radio) +{ + int rc = 0; + struct device_node *np = dev->of_node; + + radio->int_gpio = of_get_named_gpio(np, "fmint-gpio", 0); + if (radio->int_gpio < 0) { + pr_err("%s int-gpio not provided in device tree\n", __func__); + rc = radio->int_gpio; + goto err_int_gpio; + } + + rc = gpio_request(radio->int_gpio, "fm_int"); + if (rc) { + pr_err("%s unable to request gpio %d (%d)\n", __func__, + radio->int_gpio, rc); + goto err_int_gpio; + } + + rc = gpio_direction_output(radio->int_gpio, 0); + if (rc) { + pr_err("%s unable to set the gpio %d direction(%d)\n", + __func__, radio->int_gpio, rc); + goto err_int_gpio; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + + return rc; + +err_int_gpio: + gpio_free(radio->int_gpio); + + return rc; +} + +static int rtc6226_pinctrl_init(struct rtc6226_device *radio) +{ + int retval = 0; + + radio->fm_pinctrl = devm_pinctrl_get(&radio->client->dev); + if (IS_ERR_OR_NULL(radio->fm_pinctrl)) { + pr_err("%s: target does not use pinctrl\n", __func__); + retval = PTR_ERR(radio->fm_pinctrl); + return retval; + } + + radio->gpio_state_active = + pinctrl_lookup_state(radio->fm_pinctrl, + "pmx_fm_active"); + if (IS_ERR_OR_NULL(radio->gpio_state_active)) { + pr_err("%s: cannot get FM active state\n", __func__); + retval = PTR_ERR(radio->gpio_state_active); + goto err_active_state; + } + + radio->gpio_state_suspend = + pinctrl_lookup_state(radio->fm_pinctrl, + "pmx_fm_suspend"); + if (IS_ERR_OR_NULL(radio->gpio_state_suspend)) { + pr_err("%s: cannot get FM suspend state\n", __func__); + retval = PTR_ERR(radio->gpio_state_suspend); + goto err_suspend_state; + } + + return retval; + +err_suspend_state: + radio->gpio_state_suspend = 0; + +err_active_state: + radio->gpio_state_active = 0; + + return retval; +} + + +/* + * rtc6226_i2c_probe - probe for the device + */ +static int rtc6226_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rtc6226_device *radio; + struct v4l2_device *v4l2_dev; + struct v4l2_ctrl_handler *hdl; + int retval = 0; + int i = 0; + int kfifo_alloc_rc = 0; + + /* struct v4l2_ctrl *ctrl; */ + /* need to add description "irq-fm" in dts */ + + pr_info("%s enter\n", __func__); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + retval = -ENODEV; + goto err_initial; + } + + /* private data allocation and initialization */ + radio = kzalloc(sizeof(struct rtc6226_device), GFP_KERNEL); + if (!radio) { + retval = -ENOMEM; + goto err_initial; + } + + v4l2_dev = &radio->v4l2_dev; + retval = v4l2_device_register(&client->dev, v4l2_dev); + if (retval < 0) { + pr_err("%s couldn't register v4l2_device\n", __func__); + goto err_radio; + } + + pr_info("v4l2_device_register successfully\n"); + hdl = &radio->ctrl_handler; + + radio->users = 0; + radio->client = client; + mutex_init(&radio->lock); + init_completion(&radio->completion); + + retval = rtc6226_parse_dt(&client->dev, radio); + if (retval) { + pr_err("%s: Parsing DT failed(%d)\n", __func__, retval); + kfree(radio); + return retval; + } + + /* Initialize pin control*/ + retval = rtc6226_pinctrl_init(radio); + if (retval) { + pr_err("%s: rtc6226_pinctrl_init returned %d\n", + __func__, retval); + /* if pinctrl is not supported, -EINVAL is returned*/ + if (retval == -EINVAL) + retval = 0; + } else { + pr_info("%s rtc6226_pinctrl_init success\n", __func__); + } + + if (radio->ext_ldo_gpio > 0) { + retval = gpio_direction_output(radio->ext_ldo_gpio, 1); + if (retval) { + pr_err("%s Unable to set direction(LDO)\n", __func__); + kfree(radio); + return retval; + } + } + + memcpy(&radio->videodev, &rtc6226_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = v4l2_dev; + radio->videodev.ioctl_ops = &rtc6226_ioctl_ops; + video_set_drvdata(&radio->videodev, radio); + + /* rds buffer allocation */ + radio->buf_size = rds_buf * 3; + radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL); + if (!radio->buffer) { + retval = -EIO; + goto err_radio; + } + + for (i = 0; i < RTC6226_FM_BUF_MAX; i++) { + spin_lock_init(&radio->buf_lock[i]); + + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE, GFP_KERNEL); + + if (kfifo_alloc_rc != 0) { + pr_err("%s: failed allocating buffers %d\n", + __func__, kfifo_alloc_rc); + retval = -ENOMEM; + goto err; + } + } + radio->wqueue = NULL; + radio->wqueue_scan = NULL; + radio->wqueue_rds = NULL; + + /* rds buffer configuration */ + radio->wr_index = 0; + radio->rd_index = 0; + init_waitqueue_head(&radio->event_queue); + init_waitqueue_head(&radio->read_queue); + init_waitqueue_head(&rtc6226_wq); + + radio->wqueue = create_singlethread_workqueue("fmradio"); + if (!radio->wqueue) { + retval = -ENOMEM; + goto err_wqueue; + } + + radio->wqueue_scan = create_singlethread_workqueue("fmradioscan"); + if (!radio->wqueue_scan) { + retval = -ENOMEM; + goto err_wqueue_scan; + } + + radio->wqueue_rds = create_singlethread_workqueue("fmradiords"); + if (!radio->wqueue_rds) { + retval = -ENOMEM; + goto err_all; + } + + /* register video device */ + retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, + radio_nr); + if (retval) { + dev_info(&client->dev, "Could not register video device\n"); + goto err_rds; + } + + i2c_set_clientdata(client, radio); /* move from below */ + pr_info("%s exit\n", __func__); + return 0; + +err_all: + destroy_workqueue(radio->wqueue_rds); +err_wqueue_scan: + destroy_workqueue(radio->wqueue_scan); +err_rds: + kfree(radio->buffer); +err_wqueue: + destroy_workqueue(radio->wqueue); +err: + video_device_release(&radio->videodev); + v4l2_device_unregister(v4l2_dev); +err_radio: + kfree(radio); +err_initial: + return retval; +} + +/* + * rtc6226_i2c_remove - remove the device + */ +static int rtc6226_i2c_remove(struct i2c_client *client) +{ + struct rtc6226_device *radio = i2c_get_clientdata(client); + + free_irq(client->irq, radio); + kfree(radio->buffer); + video_device_release(&radio->videodev); + v4l2_ctrl_handler_free(&radio->ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2_dev); + kfree(radio); + pr_info("%s exit\n", __func__); + + return 0; +} + +#ifdef CONFIG_PM +/* + * rtc6226_i2c_suspend - suspend the device + */ +static int rtc6226_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rtc6226_device *radio = i2c_get_clientdata(client); + + pr_info("%s %d\n", __func__, radio->client->addr); + + return 0; +} + + +/* + * rtc6226_i2c_resume - resume the device + */ +static int rtc6226_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rtc6226_device *radio = i2c_get_clientdata(client); + + pr_info("%s %d\n", __func__, radio->client->addr); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(rtc6226_i2c_pm, rtc6226_i2c_suspend, + rtc6226_i2c_resume); +#endif + + +/* + * rtc6226_i2c_driver - i2c driver interface + */ +struct i2c_driver rtc6226_i2c_driver = { + .driver = { + .name = "rtc6226", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rtc6226_i2c_dt_ids), +#ifdef CONFIG_PM + .pm = &rtc6226_i2c_pm, +#endif + }, + .probe = rtc6226_i2c_probe, + .remove = rtc6226_i2c_remove, + .id_table = rtc6226_i2c_id, +}; + +/* + * rtc6226_i2c_init + */ +int rtc6226_i2c_init(void) +{ + pr_info(DRIVER_DESC ", Version " DRIVER_VERSION "\n"); + return i2c_add_driver(&rtc6226_i2c_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/media/radio/rtc6226/radio-rtc6226.h b/drivers/media/radio/rtc6226/radio-rtc6226.h new file mode 100644 index 000000000000..b3243fe3aa6d --- /dev/null +++ b/drivers/media/radio/rtc6226/radio-rtc6226.h @@ -0,0 +1,668 @@ +/* drivers/media/radio/rtc6226/radio-rtc6226.h + * + * Driver for Richwave RTC6226 FM Tuner + * + * Copyright (c) 2009 Tobias Lorenz + * Copyright (c) 2012 Hans de Goede + * Copyright (c) 2018 LG Electronics, Inc. + * Copyright (c) 2018 Richwave Technology Co.Ltd + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* driver definitions */ +/* #define _RDSDEBUG */ +#define DRIVER_NAME "rtc6226-fmtuner" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RW_Kernel_ENG + +/* driver definitions */ +#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 1) +#define DRIVER_CARD "Richwave rtc6226 FM Tuner" +#define DRIVER_DESC "I2C radio driver for rtc6226 FM Tuner" +#define DRIVER_VERSION "0.1.0" + +/************************************************************************** + * Register Definitions + **************************************************************************/ +#define RADIO_REGISTER_SIZE 2 /* 16 register bit width */ +#define RADIO_REGISTER_NUM 32 /* DEVICEID */ +#define RDS_REGISTER_NUM 6 /* STATUSRSSI */ + +#define DEVICEID 0 /* Device ID Code */ +#define DEVICE_ID 0xffff /* [15:00] Device ID */ +#define DEVICEID_PN 0xf000 /* [15:12] Part Number */ +#define DEVICEID_MFGID 0x0fff /* [11:00] Manufacturer ID */ + +#define CHIPID 1 /* Chip ID Code */ +#define CHIPID_REVISION_NO 0xfc00 /* [15:10] Chip Reversion */ + +#define MPXCFG 2 /* Power Configuration */ +#define MPXCFG_CSR0_DIS_SMUTE 0x8000 /* [15:15] Disable Softmute */ +#define MPXCFG_CSR0_DIS_MUTE 0x4000 /* [14:14] Disable Mute */ +#define MPXCFG_CSR0_MONO 0x2000 /* [13:13] Mono or Auto Detect */ +#define MPXCFG_CSR0_DEEM 0x1000 /* [12:12] DE-emphasis */ +#define MPXCFG_CSR0_VOLUME_EXT 0x0400 /* [10:10] Volume Extend */ +#define MPXCFG_CSR0_BLNDADJUST 0x0300 /* [09:08] Blending Adjust */ +#define MPXCFG_CSR0_SMUTERATE 0x00c0 /* [07:06] Softmute Rate */ +#define MPXCFG_CSR0_SMUTEATT 0x0030 /* [05:04] Softmute Attenuation */ +#define MPXCFG_CSR0_VOLUME 0x000f /* [03:00] Volume */ + +#define CHANNEL 3 /* Tuning Channel Setting */ +#define CHANNEL_CSR0_TUNE 0x8000 /* [15:15] Tune */ +#define CHANNEL_CSR0_CH 0x7fff /* [14:00] Tuning Channel */ + +#define SYSCFG 4 /* System Configuration 1 */ +#define SYSCFG_CSR0_RDSIRQEN 0x8000 /* [15:15] RDS Interrupt Enable */ +#define SYSCFG_CSR0_STDIRQEN 0x4000 /* [14:14] STD Interrupt Enable */ +#define SYSCFG_CSR0_DIS_AGC 0x2000 /* [13:13] Disable AGC */ +#define SYSCFG_CSR0_RDS_EN 0x1000 /* [12:12] RDS Enable */ +#define SYSCFG_CSR0_RBDS_M 0x0300 /* [09:08] MMBS setting */ + +#define SEEKCFG1 5 /* Seek Configuration 1 */ +#define SEEKCFG1_CSR0_SEEK 0x8000 /* [15:15] Enable Seek Function */ +#define SEEKCFG1_CSR0_SEEKUP 0x4000 /* [14:14] Seek Direction */ +#define SEEKCFG1_CSR0_SKMODE 0x2000 /* [13:13] Seek Mode */ +#define SEEKCFG1_CSR0_RSSI_LOW_TH 0x0f00 /* [11:08] RSSI Seek Threshold */ +#define SEEKCFG1_CSR0_RSSI_MONO_TH 0x000f /* [03:00] RSSI Seek Threshold */ + +#define POWERCFG 6 /* Power Configuration */ +#define POWERCFG_CSR0_ENABLE 0x8000 /* [15:15] Power-up Enable */ +#define POWERCFG_CSR0_DISABLE 0x4000 /* [14:14] Power-up Disable */ +#define POWERCFG_CSR0_BLNDOFS 0x0f00 /* [11:08] Blending Offset Value */ + +#define PADCFG 7 /* PAD Configuration */ +#define PADCFG_CSR0_GPIO 0x0004 /* [03:02] General purpose I/O */ + +#define BANKCFG 8 /* Bank Serlection */ + +#define SEEKCFG2 9 /* Seek Configuration 2 */ + +#define STATUS 10 /* Status and Work channel */ +#define STATUS_RDS_RDY 0x8000 /* [15:15] RDS Ready */ +#define STATUS_STD 0x4000 /* [14:14] Seek/Tune Done */ +#define STATUS_SF 0x2000 /* [13:13] Seek Fail */ +#define STATUS_RDS_SYNC 0x0800 /* [11:11] RDS synchronization */ +#define STATUS_SI 0x0400 /* [10:10] Stereo Indicator */ + +#define RSSI 11 /* RSSI and RDS error */ +#define RSSI_RDS_BA_ERRS 0xc000 /* [15:14] RDS Block A Errors */ +#define RSSI_RDS_BB_ERRS 0x3000 /* [15:14] RDS Block B Errors */ +#define RSSI_RDS_BC_ERRS 0x0c00 /* [13:12] RDS Block C Errors */ +#define RSSI_RDS_BD_ERRS 0x0300 /* [11:10] RDS Block D Errors */ +#define RSSI_RSSI 0x00ff /* [09:00] Read Channel */ + +#define BA_DATA 12 /* Block A data */ +#define RDSA_RDSA 0xffff /* [15:00] RDS Block A Data */ + +#define BB_DATA 13 /* Block B data */ +#define RDSB_RDSB 0xffff /* [15:00] RDS Block B Data */ + +#define BC_DATA 14 /* Block C data */ +#define RDSC_RDSC 0xffff /* [15:00] RDS Block C Data */ + +#define BD_DATA 15 /* Block D data */ +#define RDSD_RDSD 0xffff /* [15:00] RDS Block D Data */ + +#define AUDIOCFG 0x12 +#define AUDIOCFG_CSR0_VOL_AUTOFIX 0x0800 //[11:11] LSB Volume Bit Auto Fix(1) + +#define RADIOCFG 0x13 +#define CHANNEL_CSR0_CHSPACE 0x1f00 /* [12:08] Channel Sapcing */ + +#define RADIOSEEKCFG1 0x14 +/* [14:00] FM Seek Top CH, Unit 10KHz */ +#define CHANNEL_CSR0_FREQ_TOP 0x7fff + +#define RADIOSEEKCFG2 0x15 +/*[14:00] FM Seek Bottom CH, Unit 10KHz */ +#define CHANNEL_CSR0_FREQ_BOT 0x7fff + +#define I2SCFG 0x1c +/* [13:13] I2S DSP Mode(0:Normal, 1:Special) */ +#define I2S_DSP_SEL 0x2000 +/* [12:12] BCLK Polarity(0:Falling, 1:Rising) */ +#define I2S_BCLK_POL 0x1000 +/* [11:10] Word Bits Select(0:8b, 1:16b, 2:20b, 3:24b) */ +#define I2S_WD_SEL 0x0c00 +/* [09:08] Right CH Control(0:On, 1:Off, 1x:Auto) */ +#define I2S_RCH_SEL 0x0300 +/* [07:07] I2S Enable */ +#define I2S_EN 0x0080 /* [07:07] I2S Enable */ +#define I2S_MSEL 0x0040 /* [06:06] I2S Master */ +/* [05:04] I2S Output Mode(0:I2S, 1:LJ, 2:DSPA, 3:DSPB) */ +#define I2S_MODE 0x0030 +/* [03:02] I2S Sample Rate(0:32K, 1:44.1K, 2:48K) */ +#define I2S_FS_AUD_SEL 0x000c +/* [05:04] I2S BCLK Ratio(0:M32, 1:M64, 2:M128, 3:M256) */ +#define I2S_BCLK_AUD_SEL 0x0030 + +#define CHANNEL1 0x1e +#define STATUS_READCH 0x7fff /* [14:00] Read Channel */ + +#define TURN_ON 1 +#define TURN_OFF 0 +#define SRCH_UP 1 +#define SRCH_DOWN 0 + +#define WRAP_ENABLE 1 +#define WRAP_DISABLE 0 + +/* Standard buffer size */ +#define STD_BUF_SIZE 256 + +/* to distinguish between seek, tune during STC int. */ +#define NO_SEEK_TUNE_PENDING 0 +#define TUNE_PENDING 1 +#define SEEK_PENDING 2 +#define SCAN_PENDING 3 + +#define RTC6226_MIN_SRCH_MODE 0x00 +#define RTC6226_MAX_SRCH_MODE 0x02 + +#define MIN_DWELL_TIME 0x00 +#define MAX_DWELL_TIME 0x0F + +#define TUNE_STEP_SIZE 10 +#define NO_OF_RDS_BLKS 4 + +#define GET_MSB(x)((x >> 8) & 0xFF) +#define GET_LSB(x)((x) & 0xFF) + +#define OFFSET_OF_GRP_TYP 11 +#define RDS_INT_BIT 0x01 +#define FIFO_CNT_16 0x10 +#define UNCORRECTABLE_RDS_EN 0xFF01 + +/* Write starts with the upper byte of register 0x02 */ +#define WRITE_REG_NUM 3 +#define WRITE_INDEX(i) ((i + 0x02)%16) + +/* Read starts with the upper byte of register 0x0a */ +#define READ_REG_NUM 2 +#define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM) + +#define MSB_OF_BLK_0 4 +#define LSB_OF_BLK_0 5 +#define MSB_OF_BLK_1 6 +#define LSB_OF_BLK_1 7 +#define MSB_OF_BLK_2 8 +#define LSB_OF_BLK_2 9 +#define MSB_OF_BLK_3 10 +#define LSB_OF_BLK_3 11 +#define MAX_RT_LEN 64 +#define END_OF_RT 0x0d +#define MAX_PS_LEN 8 +#define OFFSET_OF_PS 5 +#define PS_VALIDATE_LIMIT 2 +#define RT_VALIDATE_LIMIT 2 +#define RDS_CMD_LEN 3 +#define RDS_RSP_LEN 13 +#define PS_EVT_DATA_LEN (MAX_PS_LEN + OFFSET_OF_PS) +#define NO_OF_PS 1 +#define OFFSET_OF_RT 5 +#define OFFSET_OF_PTY 5 +#define MAX_LEN_2B_GRP_RT 32 +#define CNT_FOR_2A_GRP_RT 4 +#define CNT_FOR_2B_GRP_RT 2 +#define PS_MASK 0x3 +#define PTY_MASK 0x1F +#define NO_OF_CHARS_IN_EACH_ADD 2 + +#define CORRECTED_NONE 0 +#define CORRECTED_ONE_TO_TWO 1 +#define CORRECTED_THREE_TO_FIVE 2 +#define ERRORS_CORRECTED(data, block) ((data>>block)&0x03) +/*Block Errors are reported in .5% increments*/ +#define BLER_SCALE_MAX 200 + +/* freqs are divided by 10. */ +#define SCALE_AF_CODE_TO_FREQ_KHZ(x) (87500 + (x*100)) + +#define RDS_TYPE_0A (0 * 2 + 0) +#define RDS_TYPE_0B (0 * 2 + 1) +#define RDS_TYPE_2A (2 * 2 + 0) +#define RDS_TYPE_2B (2 * 2 + 1) +#define RDS_TYPE_3A (3 * 2 + 0) +#define UNCORRECTABLE 3 + +#define APP_GRP_typ_MASK 0x1F +/*ERT*/ +#define ERT_AID 0x6552 +#define MAX_ERT_SEGMENT 31 +#define MAX_ERT_LEN 256 +#define ERT_OFFSET 3 +#define ERT_FORMAT_DIR_BIT 1 +#define ERT_CNT_PER_BLK 2 +/*RT PLUS*/ +#define DUMMY_CLASS 0 +#define RT_PLUS_LEN_1_TAG 3 +#define RT_ERT_FLAG_BIT 13 +#define RT_PLUS_AID 0x4bd7 +#define RT_ERT_FLAG_OFFSET 1 +#define RT_PLUS_OFFSET 2 +/*TAG1*/ +#define TAG1_MSB_OFFSET 3 +#define TAG1_MSB_MASK 7 +#define TAG1_LSB_OFFSET 13 +#define TAG1_POS_MSB_MASK 0x3F +#define TAG1_POS_MSB_OFFSET 1 +#define TAG1_POS_LSB_OFFSET 7 +#define TAG1_LEN_OFFSET 1 +#define TAG1_LEN_MASK 0x3F +/*TAG2*/ +#define TAG2_MSB_OFFSET 5 +#define TAG2_MSB_MASK 9 +#define TAG2_LSB_OFFSET 11 +#define TAG2_POS_MSB_MASK 0x3F +#define TAG2_POS_MSB_OFFSET 3 +#define TAG2_POS_LSB_OFFSET 5 +#define TAG2_LEN_MASK 0x1F + +#define DEFAULT_AF_RSSI_LOW_TH 25 +#define NO_OF_AF_IN_GRP 2 +#define MAX_NO_OF_AF 25 +#define MAX_AF_LIST_SIZE (MAX_NO_OF_AF * 4) /* 4 bytes per freq */ +#define GET_AF_EVT_LEN(x) (7 + x*4) +#define GET_AF_LIST_LEN(x) (x*4) +#define MIN_AF_FREQ_CODE 1 +#define MAX_AF_FREQ_CODE 204 + +/* 25 AFs supported for a freq. 224 means 1 AF. 225 means 2 AFs and so on */ +#define NO_AF_CNT_CODE 224 +#define MIN_AF_CNT_CODE 225 +#define MAX_AF_CNT_CODE 249 +#define AF_WAIT_SEC 10 +#define MAX_AF_WAIT_SEC 255 +#define AF_PI_WAIT_TIME 50 /* 50*100msec = 5sec */ + +#define CH_SPACING_200 200 +#define CH_SPACING_100 100 +#define CH_SPACING_50 50 + +#define RW_PRIBASE (V4L2_CID_USER_BASE | 0xf000) + +/* freqs are divided by 10. */ +#define SCALE_AF_CODE_TO_FREQ_KHZ(x) (87500 + (x*100)) + +#define EXTRACT_BIT(data, bit_pos) ((data >> bit_pos) & 1) + +#define V4L2_CID_PRIVATE_CSR0_ENABLE (RW_PRIBASE + (DEVICEID<<4) + 1) +#define V4L2_CID_PRIVATE_CSR0_DISABLE (RW_PRIBASE + (DEVICEID<<4) + 2) +#define V4L2_CID_PRIVATE_DEVICEID (RW_PRIBASE + (DEVICEID<<4) + 3) + +#define V4L2_CID_PRIVATE_CSR0_DIS_SMUTE (RW_PRIBASE + (DEVICEID<<4) + 4) +#define V4L2_CID_PRIVATE_CSR0_DIS_MUTE (RW_PRIBASE + (DEVICEID<<4) + 5) +#define V4L2_CID_PRIVATE_CSR0_DEEM (RW_PRIBASE + (DEVICEID<<4) + 6) +#define V4L2_CID_PRIVATE_CSR0_BLNDADJUST (RW_PRIBASE + (DEVICEID<<4) + 7) +#define V4L2_CID_PRIVATE_CSR0_VOLUME (RW_PRIBASE + (DEVICEID<<4) + 8) + +#define V4L2_CID_PRIVATE_CSR0_BAND (RW_PRIBASE + (DEVICEID<<4) + 9) +#define V4L2_CID_PRIVATE_CSR0_CHSPACE (RW_PRIBASE + (DEVICEID<<4) + 10) + +#define V4L2_CID_PRIVATE_CSR0_DIS_AGC (RW_PRIBASE + (DEVICEID<<4) + 11) +#define V4L2_CID_PRIVATE_CSR0_RDS_EN (RW_PRIBASE + (DEVICEID<<4) + 12) + +#define V4L2_CID_PRIVATE_SEEK_CANCEL (RW_PRIBASE + (DEVICEID<<4) + 13) + +#define V4L2_CID_PRIVATE_CSR0_SEEKRSSITH (RW_PRIBASE + (DEVICEID<<4) + 14) +#define V4L2_CID_PRIVATE_RSSI (RW_PRIBASE + (CHIPID<<4) + 1) + +#define V4L2_CID_PRIVATE_RDS_RDY (RW_PRIBASE + (CHIPID<<4) + 2) +#define V4L2_CID_PRIVATE_STD (RW_PRIBASE + (CHIPID<<4) + 3) +#define V4L2_CID_PRIVATE_SF (RW_PRIBASE + (CHIPID<<4) + 4) +#define V4L2_CID_PRIVATE_RDS_SYNC (RW_PRIBASE + (CHIPID<<4) + 5) +#define V4L2_CID_PRIVATE_SI (RW_PRIBASE + (CHIPID<<4) + 6) + +#define WAIT_OVER 0 +#define SEEK_WAITING 1 +#define NO_WAIT 2 +#define TUNE_WAITING 4 +#define RDS_WAITING 5 +#define SEEK_CANCEL 6 +/************************************************************************** + * General Driver Definitions + **************************************************************************/ + +enum rtc6226_buf_t { + RTC6226_FM_BUF_SRCH_LIST, + RTC6226_FM_BUF_EVENTS, + RTC6226_FM_BUF_RT_RDS, + RTC6226_FM_BUF_PS_RDS, + RTC6226_FM_BUF_RAW_RDS, + RTC6226_FM_BUF_AF_LIST, + RTC6226_FM_BUF_RT_PLUS = 11, + RTC6226_FM_BUF_ERT, + RTC6226_FM_BUF_MAX +}; + +enum rtc6226_evt_t { + RTC6226_EVT_RADIO_READY, + RTC6226_EVT_TUNE_SUCC, + RTC6226_EVT_SEEK_COMPLETE, + RTC6226_EVT_SCAN_NEXT, + RTC6226_EVT_NEW_RAW_RDS, + RTC6226_EVT_NEW_RT_RDS, + RTC6226_EVT_NEW_PS_RDS, + RTC6226_EVT_ERROR, + RTC6226_EVT_BELOW_TH, + RTC6226_EVT_ABOVE_TH, + RTC6226_EVT_STEREO, + RTC6226_EVT_MONO, + RTC6226_EVT_RDS_AVAIL, + RTC6226_EVT_RDS_NOT_AVAIL, + RTC6226_EVT_NEW_SRCH_LIST, + RTC6226_EVT_NEW_AF_LIST, + RTC6226_EVT_TXRDSDAT, + RTC6226_EVT_TXRDSDONE, + RTC6226_EVT_RADIO_DISABLED, + RTC6226_EVT_NEW_ODA, + RTC6226_EVT_NEW_RT_PLUS, + RTC6226_EVT_NEW_ERT +}; + +struct rtc6226_recv_conf_req { + __u16 emphasis; + __u16 ch_spacing; + /* limits stored as actual freq / TUNE_STEP_SIZE */ + __u16 band_low_limit; + __u16 band_high_limit; +}; + +struct rtc6226_rel_freq { + __u8 rel_freq_msb; + __u8 rel_freq_lsb; +} __packed; + +struct rtc6226_srch_list_compl { + __u8 num_stations_found; + struct rtc6226_rel_freq rel_freq[20]; +} __packed; + +struct af_list_ev { + __le32 tune_freq_khz; + __le16 pi_code; + __u8 af_size; + __u8 af_list[MAX_AF_LIST_SIZE]; +} __packed; + +struct rtc6226_af_info { + /* no. of invalid AFs. */ + u8 inval_freq_cnt; + /* no. of AFs in the list. */ + u8 cnt; + /* actual size of the list */ + u8 size; + /* index of currently tuned station in the AF list. */ + u8 index; + /* PI of the frequency */ + u16 pi; + /* freq to which AF list belongs to. */ + u32 orig_freq_khz; + /* AF list */ + u32 af_list[MAX_NO_OF_AF]; +}; + +/* + * rtc6226_device - private data + */ +struct rtc6226_device { + int int_gpio; + int fm_sw_gpio; + int ext_ldo_gpio; + int reset_gpio; + struct regulator *vdd_reg; + struct v4l2_device v4l2_dev; + struct video_device videodev; + struct pinctrl *fm_pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + struct v4l2_ctrl_handler ctrl_handler; + int band; + int space; + unsigned int users; + unsigned int mode; + u8 seek_tune_status; + u8 rssi_th; + /* Richwave internal registers (0..15) */ + unsigned short registers[RADIO_REGISTER_NUM]; + + /* RDS receive buffer */ + wait_queue_head_t read_queue; + int irq; + int tuned_freq_khz; + int dwell_time_sec; + struct mutex lock; /* buffer locking */ + unsigned char *buffer; /* size is always multiple of three */ + bool is_search_cancelled; + u8 g_search_mode; + struct rtc6226_srch_list_compl srch_list; + /* buffer locks*/ + spinlock_t buf_lock[RTC6226_FM_BUF_MAX]; + struct rtc6226_recv_conf_req recv_conf; + struct workqueue_struct *wqueue; + struct workqueue_struct *wqueue_scan; + struct workqueue_struct *wqueue_rds; + struct work_struct rds_worker; + struct rtc6226_af_info af_info1; + struct rtc6226_af_info af_info2; + + struct delayed_work work; + struct delayed_work work_scan; + + wait_queue_head_t event_queue; + u8 write_buf[WRITE_REG_NUM]; + /* TO read events, data*/ + u8 read_buf[READ_REG_NUM]; + + u16 pi; /* PI of tuned channel */ + u8 pty; /* programe type of the tuned channel */ + + u16 block[NO_OF_RDS_BLKS]; + u8 rt_display[MAX_RT_LEN]; /* RT that will be displayed */ + u8 rt_tmp0[MAX_RT_LEN]; /* high probability RT */ + u8 rt_tmp1[MAX_RT_LEN]; /* low probability RT */ + u8 rt_cnt[MAX_RT_LEN]; /* high probability RT's hit count */ + u8 rt_flag; /* A/B flag of RT */ + bool valid_rt_flg; /* validity of A/B flag */ + u8 ps_display[MAX_PS_LEN]; /* PS that will be displayed */ + u8 ps_tmp0[MAX_PS_LEN]; /* high probability PS */ + u8 ps_tmp1[MAX_PS_LEN]; /* low probability PS */ + u8 ps_cnt[MAX_PS_LEN]; /* high probability PS's hit count */ + u8 bler[NO_OF_RDS_BLKS]; + u8 rt_plus_carrier; + u8 ert_carrier; + u8 ert_buf[MAX_ERT_LEN]; + u8 ert_len; + u8 c_byt_pair_index; + u8 utf_8_flag; + u8 rt_ert_flag; + u8 formatting_dir; + unsigned int buf_size; + unsigned int rd_index; + unsigned int wr_index; + + struct kfifo data_buf[RTC6226_FM_BUF_MAX]; + + struct completion completion; + bool stci_enabled; /* Seek/Tune Complete Interrupt */ + + struct i2c_client *client; + unsigned int tuner_state; + int lna_en; + int lna_gain; +}; + +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_RESET, + FM_CALIB, + FM_TURNING_OFF, + FM_RECV_TURNING_ON, + FM_MAX_NO_STATES, +}; + +enum search_t { + SEEK, + SCAN, + SCAN_FOR_STRONG, +}; + +/************************************************************************** + * Frequency Multiplicator + **************************************************************************/ +#define FREQ_MUL 1000 +#define CONFIG_RDS + +enum v4l2_cid_private_rtc6226_t { + V4L2_CID_PRIVATE_RTC6226_SRCHMODE = (V4L2_CID_PRIVATE_BASE + 1), + V4L2_CID_PRIVATE_RTC6226_SCANDWELL, + V4L2_CID_PRIVATE_RTC6226_SRCHON, + V4L2_CID_PRIVATE_RTC6226_STATE, + V4L2_CID_PRIVATE_RTC6226_TRANSMIT_MODE, + V4L2_CID_PRIVATE_RTC6226_RDSGROUP_MASK, + V4L2_CID_PRIVATE_RTC6226_REGION, + V4L2_CID_PRIVATE_RTC6226_SIGNAL_TH, + V4L2_CID_PRIVATE_RTC6226_SRCH_PTY, + V4L2_CID_PRIVATE_RTC6226_SRCH_PI, + V4L2_CID_PRIVATE_RTC6226_SRCH_CNT, + V4L2_CID_PRIVATE_RTC6226_EMPHASIS, /* 800000c */ + V4L2_CID_PRIVATE_RTC6226_RDS_STD, + V4L2_CID_PRIVATE_RTC6226_SPACING, + V4L2_CID_PRIVATE_RTC6226_RDSON, + V4L2_CID_PRIVATE_RTC6226_RDSGROUP_PROC, + V4L2_CID_PRIVATE_RTC6226_LP_MODE, + V4L2_CID_PRIVATE_RTC6226_ANTENNA, + V4L2_CID_PRIVATE_RTC6226_RDSD_BUF, + V4L2_CID_PRIVATE_RTC6226_PSALL, + /*v4l2 Tx controls*/ + V4L2_CID_PRIVATE_RTC6226_TX_SETPSREPEATCOUNT, + V4L2_CID_PRIVATE_RTC6226_STOP_RDS_TX_PS_NAME, + V4L2_CID_PRIVATE_RTC6226_STOP_RDS_TX_RT, + V4L2_CID_PRIVATE_RTC6226_IOVERC, + V4L2_CID_PRIVATE_RTC6226_INTDET, + V4L2_CID_PRIVATE_RTC6226_MPX_DCC, + V4L2_CID_PRIVATE_RTC6226_AF_JUMP, + V4L2_CID_PRIVATE_RTC6226_RSSI_DELTA, + V4L2_CID_PRIVATE_RTC6226_HLSI, + + /* + * Here we have IOCTl's that are specific to IRIS + * (V4L2_CID_PRIVATE_BASE + 0x1E to V4L2_CID_PRIVATE_BASE + 0x28) + */ + V4L2_CID_PRIVATE_RTC6226_SOFT_MUTE,/* 0x800001E*/ + V4L2_CID_PRIVATE_RTC6226_RIVA_ACCS_ADDR, + V4L2_CID_PRIVATE_RTC6226_RIVA_ACCS_LEN, + V4L2_CID_PRIVATE_RTC6226_RIVA_PEEK, + V4L2_CID_PRIVATE_RTC6226_RIVA_POKE, + V4L2_CID_PRIVATE_RTC6226_SSBI_ACCS_ADDR, + V4L2_CID_PRIVATE_RTC6226_SSBI_PEEK, + V4L2_CID_PRIVATE_RTC6226_SSBI_POKE, + V4L2_CID_PRIVATE_RTC6226_TX_TONE, + V4L2_CID_PRIVATE_RTC6226_RDS_GRP_COUNTERS, + V4L2_CID_PRIVATE_RTC6226_SET_NOTCH_FILTER, /* 0x8000028 */ + + V4L2_CID_PRIVATE_RTC6226_SET_AUDIO_PATH, /* 0x8000029 */ + V4L2_CID_PRIVATE_RTC6226_DO_CALIBRATION, /* 0x800002A : IRIS */ + V4L2_CID_PRIVATE_RTC6226_SRCH_ALGORITHM, /* 0x800002B */ + V4L2_CID_PRIVATE_RTC6226_GET_SINR, /* 0x800002C : IRIS */ + V4L2_CID_PRIVATE_RTC6226_INTF_LOW_THRESHOLD, /* 0x800002D */ + V4L2_CID_PRIVATE_RTC6226_INTF_HIGH_THRESHOLD, /* 0x800002E */ + /* 0x800002F : IRIS, For Richwave Spike TH */ + V4L2_CID_PRIVATE_RTC6226_SINR_THRESHOLD, + /* V4L2_CID_PRIVATE_RTC6226_QLT_THRESHOLD, + */ /* 0x800002F : IRIS, For Richwave Spike TH + */ + V4L2_CID_PRIVATE_RTC6226_SINR_SAMPLES, /* 0x8000030 : IRIS */ + V4L2_CID_PRIVATE_RTC6226_SPUR_FREQ, + V4L2_CID_PRIVATE_RTC6226_SPUR_FREQ_RMSSI, /* For Richwave DC TH */ + /* V4L2_CID_PRIVATE_RTC6226_OFS_THRESHOLD, */ /* For Richwave DC TH */ + V4L2_CID_PRIVATE_RTC6226_SPUR_SELECTION, + V4L2_CID_PRIVATE_RTC6226_UPDATE_SPUR_TABLE, + V4L2_CID_PRIVATE_RTC6226_VALID_CHANNEL, + V4L2_CID_PRIVATE_RTC6226_AF_RMSSI_TH, + V4L2_CID_PRIVATE_RTC6226_AF_RMSSI_SAMPLES, + V4L2_CID_PRIVATE_RTC6226_GOOD_CH_RMSSI_TH, + V4L2_CID_PRIVATE_RTC6226_SRCHALGOTYPE, + V4L2_CID_PRIVATE_RTC6226_CF0TH12, + V4L2_CID_PRIVATE_RTC6226_SINRFIRSTSTAGE, + V4L2_CID_PRIVATE_RTC6226_RMSSIFIRSTSTAGE, + V4L2_CID_PRIVATE_RTC6226_RXREPEATCOUNT, + V4L2_CID_PRIVATE_RTC6226_RSSI_TH, /* 0x800003E */ + V4L2_CID_PRIVATE_RTC6226_AF_JUMP_RSSI_TH /* 0x800003F */ +}; + +enum FMBAND {FMBAND_87_108_MHZ, FMBAND_76_108_MHZ, FMBAND_76_91_MHZ, + FMBAND_64_76_MHZ}; +enum FMSPACE {FMSPACE_200_KHZ, FMSPACE_100_KHZ, FMSPACE_50_KHZ}; + + +/************************************************************************** + * Common Functions + **************************************************************************/ +extern struct i2c_driver rtc6226_i2c_driver; +extern struct video_device rtc6226_viddev_template; +extern const struct v4l2_ioctl_ops rtc6226_ioctl_ops; +extern const struct v4l2_ctrl_ops rtc6226_ctrl_ops; + +extern struct tasklet_struct my_tasklet; +extern int rtc6226_wq_flag; +extern wait_queue_head_t rtc6226_wq; +extern int rtc6226_get_all_registers(struct rtc6226_device *radio); +extern int rtc6226_get_register(struct rtc6226_device *radio, int regnr); +extern int rtc6226_set_register(struct rtc6226_device *radio, int regnr); +extern int rtc6226_set_serial_registers(struct rtc6226_device *radio, + u16 *data, int bytes); +int rtc6226_i2c_init(void); +int rtc6226_reset_rds_data(struct rtc6226_device *radio); +int rtc6226_set_freq(struct rtc6226_device *radio, unsigned int freq); +int rtc6226_start(struct rtc6226_device *radio); +int rtc6226_stop(struct rtc6226_device *radio); +int rtc6226_fops_open(struct file *file); +int rtc6226_power_up(struct rtc6226_device *radio); +int rtc6226_power_down(struct rtc6226_device *radio); +int rtc6226_fops_release(struct file *file); +int rtc6226_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability); +int rtc6226_enable_irq(struct rtc6226_device *radio); +void rtc6226_disable_irq(struct rtc6226_device *radio); +void rtc6226_scan(struct work_struct *work); +void rtc6226_search(struct rtc6226_device *radio, bool on); +int rtc6226_cancel_seek(struct rtc6226_device *radio); +void rtc6226_rds_handler(struct work_struct *worker);