[PATCH 2/2] misc: added Spreadtrum's radio driver

From: Chunyan Zhang
Date: Tue Jul 04 2017 - 06:22:35 EST


This patch added FM radio driver for Spreadtrum's SC2342, which's
a WCN SoC, also added a new directory for Spreadtrum's WCN SoCs.

Signed-off-by: Songhe Wei <songhe.wei@xxxxxxxxxxxxxx>
Signed-off-by: Chunyan Zhang <chunyan.zhang@xxxxxxxxxxxxxx>
---
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/sprd-wcn/Kconfig | 14 +
drivers/misc/sprd-wcn/Makefile | 1 +
drivers/misc/sprd-wcn/radio/Kconfig | 8 +
drivers/misc/sprd-wcn/radio/Makefile | 2 +
drivers/misc/sprd-wcn/radio/fmdrv.h | 595 +++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_main.c | 1245 ++++++++++++++++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_main.h | 117 +++
drivers/misc/sprd-wcn/radio/fmdrv_ops.c | 447 +++++++++
drivers/misc/sprd-wcn/radio/fmdrv_ops.h | 17 +
drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c | 753 ++++++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h | 103 ++
13 files changed, 3304 insertions(+)
create mode 100644 drivers/misc/sprd-wcn/Kconfig
create mode 100644 drivers/misc/sprd-wcn/Makefile
create mode 100644 drivers/misc/sprd-wcn/radio/Kconfig
create mode 100644 drivers/misc/sprd-wcn/radio/Makefile
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 07bbd4c..5e295b3 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -510,4 +510,5 @@ source "drivers/misc/mic/Kconfig"
source "drivers/misc/genwqe/Kconfig"
source "drivers/misc/echo/Kconfig"
source "drivers/misc/cxl/Kconfig"
+source "drivers/misc/sprd-wcn/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index ad13677..df75ea7 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
+obj-$(CONFIG_SPRD_WCN) += sprd-wcn/

lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
diff --git a/drivers/misc/sprd-wcn/Kconfig b/drivers/misc/sprd-wcn/Kconfig
new file mode 100644
index 0000000..d2e7428
--- /dev/null
+++ b/drivers/misc/sprd-wcn/Kconfig
@@ -0,0 +1,14 @@
+config SPRD_WCN
+ tristate "Support for Spreadtrum's WCN SoCs"
+ depends on ARCH_SPRD
+ default n
+ help
+ This enables Spreadtrum's WCN (wireless connectivity network)
+ SoCs. In general, Spreadtrum's WCN SoCs consisted of some
+ modules, such as FM, bluetooth, wifi, GPS, etc.
+
+if SPRD_WCN
+
+source "drivers/misc/sprd-wcn/radio/Kconfig"
+
+endif
diff --git a/drivers/misc/sprd-wcn/Makefile b/drivers/misc/sprd-wcn/Makefile
new file mode 100644
index 0000000..3ad5dad
--- /dev/null
+++ b/drivers/misc/sprd-wcn/Makefile
@@ -0,0 +1 @@
+obj-y += radio/
diff --git a/drivers/misc/sprd-wcn/radio/Kconfig b/drivers/misc/sprd-wcn/radio/Kconfig
new file mode 100644
index 0000000..3cc0f7e
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/Kconfig
@@ -0,0 +1,8 @@
+## Spreadtrum SC2332 FM drivers
+
+config SPRD_RADIO_SC2332
+ tristate "Support for the Spreadtrum Radio SC2332"
+ default n
+ ---help---
+ Say Y to enable built-in FM radio controller for the
+ Spreadtrum SC2332 SoC.
diff --git a/drivers/misc/sprd-wcn/radio/Makefile b/drivers/misc/sprd-wcn/radio/Makefile
new file mode 100644
index 0000000..16f1582
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_SPRD_RADIO_SC2332) := marlin2_fm.o
+marlin2_fm-objs := fmdrv_main.o fmdrv_ops.o fmdrv_rds_parser.o
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv.h b/drivers/misc/sprd-wcn/radio/fmdrv.h
new file mode 100644
index 0000000..e74ff7f
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv.h
@@ -0,0 +1,595 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _FM_DRV_H
+#define _FM_DRV_H
+
+#include <linux/completion.h>
+#include <linux/ioctl.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+
+#define FM_DEV_NAME "fm"
+#define FM_RDS_ENABLE 0x01
+#define MARLIN_FM 0
+
+/* scan sort algorithm */
+enum {
+ FM_SCAN_SORT_NON = 0,
+ FM_SCAN_SORT_UP,
+ FM_SCAN_SORT_DOWN,
+ FM_SCAN_SORT_MAX
+};
+
+/* scan methods */
+enum {
+ /* select hardware scan, advantage: fast */
+ FM_SCAN_SEL_HW = 0,
+ /* select software scan, advantage: more accurate */
+ FM_SCAN_SEL_SW,
+ FM_SCAN_SEL_MAX
+};
+
+/* FM config for customer */
+/* FM radio long antenna RSSI threshold(11.375dBuV) */
+#define FMR_RSSI_TH_LONG 0x0301
+/* FM radio short antenna RSSI threshold(-1dBuV) */
+#define FMR_RSSI_TH_SHORT 0x02E0
+/* FM radio Channel quality indicator threshold(0x0000~0x00FF) */
+#define FMR_CQI_TH 0x00E9
+/* FM radio seek space,1:100KHZ; 2:200KHZ */
+#define FMR_SEEK_SPACE 1
+/* FM radio scan max channel size */
+#define FMR_SCAN_CH_SIZE 40
+/* FM radio band, 1:87.5MHz~108.0MHz;*/
+/* 2:76.0MHz~90.0MHz;*/
+/* 3:76.0MHz~108.0MHz; 4:special */
+#define FMR_BAND 1
+/* FM radio special band low freq(Default 87.5MHz) */
+#define FMR_BAND_FREQ_L 875
+/* FM radio special band high freq(Default 108.0MHz) */
+#define FMR_BAND_FREQ_H 1080
+#define FM_SCAN_SORT_SELECT FM_SCAN_SORT_NON
+#define FM_SCAN_SELECT FM_SCAN_SEL_HW
+/* soft-mute threshold when software scan, rang: 0~3, */
+/* 0 means better audio quality but less channel */
+#define FM_SCAN_SOFT_MUTE_GAIN_TH 3
+/* rang: -102 ~ -72 */
+#define FM_CHIP_DESE_RSSI_TH (-102)
+
+/* FM config for engineer */
+/* FM radio MR threshold */
+#define FMR_MR_TH 0x01BD
+/* scan thrshold register */
+#define ADDR_SCAN_TH 0xE0
+/* scan CQI register */
+#define ADDR_CQI_TH 0xE1
+/* 4 sec */
+#define FM_DRV_TX_TIMEOUT (4*HZ)
+/* 20 sec */
+#define FM_DRV_RX_SEEK_TIMEOUT (20*HZ)
+
+/* errno */
+#define FM_SUCCESS 0
+#define FM_FAILED 1
+#define FM_EPARM 2
+#define FM_BADSTATUS 3
+#define FM_TUNE_FAILED 4
+#define FM_SEEK_FAILED 5
+#define FM_BUSY 6
+#define FM_SCAN_FAILED 7
+
+/* band */
+#define FM_BAND_UNKNOWN 0
+/* US/Europe band 87.5MHz ~ 108MHz (DEFAULT) */
+#define FM_BAND_UE 1
+/* Japan band 76MHz ~ 90MHz */
+#define FM_BAND_JAPAN 2
+/* Japan wideband 76MHZ ~ 108MHz */
+#define FM_BAND_JAPANW 3
+/* special band between 76MHZ and 108MHz */
+#define FM_BAND_SPECIAL 4
+#define FM_BAND_DEFAULT FM_BAND_UE
+
+#define FM_UE_FREQ_MIN 875
+#define FM_UE_FREQ_MAX 1080
+#define FM_JP_FREQ_MIN 760
+#define FM_JP_FREQ_MAX 1080
+#define FM_FREQ_MIN FMR_BAND_FREQ_L
+#define FM_FREQ_MAX FMR_BAND_FREQ_H
+#define FM_RAIDO_BAND FM_BAND_UE
+
+/* space */
+#define FM_SPACE_UNKNOWN 0
+#define FM_SPACE_100K 1
+#define FM_SPACE_200K 2
+#define FM_SPACE_50K 5
+#define FM_SPACE_DEFAULT FM_SPACE_100K
+
+#define FM_SEEK_SPACE FMR_SEEK_SPACE
+
+/* max scan channel num */
+#define FM_MAX_CHL_SIZE FMR_SCAN_CH_SIZE
+/* auto HiLo */
+#define FM_AUTO_HILO_OFF 0
+#define FM_AUTO_HILO_ON 1
+
+/* seek direction */
+#define FM_SEEK_UP 0
+#define FM_SEEK_DOWN 1
+
+#define FM_VERSION "v0.0"
+
+/* seek threshold */
+#define FM_SEEKTH_LEVEL_DEFAULT 4
+
+struct fm_tune_parm {
+ uint8_t err;
+ uint8_t band;
+ uint8_t space;
+ uint8_t hilo;
+ uint16_t freq;
+};
+
+struct fm_seek_parm {
+ uint8_t err;
+ uint8_t band;
+ uint8_t space;
+ uint8_t hilo;
+ uint8_t seekdir;
+ uint8_t seekth;
+ uint16_t freq;
+};
+
+/* Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI */
+/* Frequency_Offset_Th [0x0000 0xFFFF] EXPERIENCE VALUES:0x5dc */
+/* Pilot_Power_Th RANGES: [0x0000 0x1FFF] EXPERIENCE VALUES:0x190 */
+/* Noise_Power_Th RANGES: [0x0000 0x1FFF] EXPERIENCE VALUES:0xB0 */
+struct fm_seek_criteria_parm {
+ unsigned char rssi_th;
+ unsigned char snr_th;
+ unsigned short freq_offset_th;
+ unsigned short pilot_power_th;
+ unsigned short noise_power_th;
+} __packed;
+
+struct fm_audio_threshold_parm {
+ unsigned short hbound;
+ unsigned short lbound;
+ unsigned short power_th;
+ unsigned char phyt;
+ unsigned char snr_th;
+} __packed;
+/*__attribute__ ((packed));*/
+
+struct fm_reg_ctl_parm {
+ unsigned char err;
+ unsigned int addr;
+ unsigned int val;
+ /*0:write, 1:read*/
+ unsigned char rw_flag;
+} __packed;
+
+struct fm_scan_parm {
+ uint8_t err;
+ uint8_t band;
+ uint8_t space;
+ uint8_t hilo;
+ uint16_t freq;
+ uint16_t scantbl[16];
+ uint16_t scantblsize;
+};
+
+struct fm_scan_all_parm {
+ unsigned char band;/*87.5~108,76~*/
+ unsigned char space;/*50 or 100KHz */
+ unsigned char chanel_num;
+ unsigned short freq[36]; /* OUT parameter*/
+};
+
+struct fm_ch_rssi {
+ uint16_t freq;
+ int rssi;
+};
+
+enum fm_scan_cmd_t {
+ FM_SCAN_CMD_INIT = 0,
+ FM_SCAN_CMD_START,
+ FM_SCAN_CMD_GET_NUM,
+ FM_SCAN_CMD_GET_CH,
+ FM_SCAN_CMD_GET_RSSI,
+ FM_SCAN_CMD_GET_CH_RSSI,
+ FM_SCAN_CMD_MAX
+};
+
+struct fm_rssi_req {
+ uint16_t num;
+ uint16_t read_cnt;
+ struct fm_ch_rssi cr[16*16];
+};
+
+struct fm_hw_info {
+ int chip_id;
+ int eco_ver;
+ int rom_ver;
+ int patch_ver;
+ int reserve;
+};
+
+struct rdslag {
+ uint8_t TP;
+ uint8_t TA;
+ uint8_t music;
+ uint8_t stereo;
+ uint8_t artificial_head;
+ uint8_t compressed;
+ uint8_t dynamic_pty;
+ uint8_t text_ab;
+ uint32_t flag_status;
+};
+
+struct ct_info {
+ uint16_t month;
+ uint16_t day;
+ uint16_t year;
+ uint16_t hour;
+ uint16_t minute;
+ uint8_t local_time_offset_signbit;
+ uint8_t local_time_offset_half_hour;
+};
+
+struct af_info {
+ int16_t AF_NUM;
+ int16_t AF[2][25];
+ uint8_t addr_cnt;
+ uint8_t ismethod_a;
+ uint8_t isafnum_get;
+};
+
+struct ps_info {
+ uint8_t PS[4][8];
+ uint8_t addr_cnt;
+};
+
+struct rt_info {
+ uint8_t textdata[4][64];
+ uint8_t getlength;
+ uint8_t isrtdisplay;
+ uint8_t textlength;
+ uint8_t istypea;
+ uint8_t bufcnt;
+ uint16_t addr_cnt;
+};
+
+struct rds_raw_data {
+ /* indicate if the data changed or not */
+ int dirty;
+ /* the data len form chip */
+ int len;
+ uint8_t data[146];
+};
+
+struct rds_group_cnt {
+ unsigned int total;
+ unsigned int groupA[16];
+ unsigned int groupB[16];
+};
+
+enum rds_group_cnt_opcode {
+ RDS_GROUP_CNT_READ = 0,
+ RDS_GROUP_CNT_WRITE,
+ RDS_GROUP_CNT_RESET,
+ RDS_GROUP_CNT_MAX
+};
+
+struct rds_group_cnt_req {
+ int err;
+ enum rds_group_cnt_opcode op;
+ struct rds_group_cnt gc;
+};
+
+struct fm_rds_data {
+ struct ct_info CT;
+ struct rdslag RDSFLAG;
+ uint16_t PI;
+ uint8_t switch_tp;
+ uint8_t PTY;
+ struct af_info af_data;
+ struct af_info afon_data;
+ uint8_t radio_page_code;
+ uint16_t program_item_number_code;
+ uint8_t extend_country_code;
+ uint16_t language_code;
+ struct ps_info ps_data;
+ uint8_t ps_on[8];
+ struct rt_info rt_data;
+ uint16_t event_status;
+ struct rds_group_cnt gc;
+};
+
+/* valid Rds Flag for notify */
+enum {
+ /* Program is a traffic program */
+ RDS_FLAG_IS_TP = 0x0001,
+ /* Program currently broadcasts a traffic ann. */
+ RDS_FLAG_IS_TA = 0x0002,
+ /* Program currently broadcasts music */
+ RDS_FLAG_IS_MUSIC = 0x0004,
+ /* Program is transmitted in stereo */
+ RDS_FLAG_IS_STEREO = 0x0008,
+ /* Program is an artificial head recording */
+ RDS_FLAG_IS_ARTIFICIAL_HEAD = 0x0010,
+ /* Program content is compressed */
+ RDS_FLAG_IS_COMPRESSED = 0x0020,
+ /* Program type can change */
+ RDS_FLAG_IS_DYNAMIC_PTY = 0x0040,
+ /* If this flag changes state, a new radio text string begins */
+ RDS_FLAG_TEXT_AB = 0x0080
+};
+
+enum {
+ /* One of the RDS flags has changed state */
+ RDS_EVENT_FLAGS = 0x0001,
+ /* The program identification code has changed */
+ RDS_EVENT_PI_CODE = 0x0002,
+ /* The program type code has changed */
+ RDS_EVENT_PTY_CODE = 0x0004,
+ /* The program name has changed */
+ RDS_EVENT_PROGRAMNAME = 0x0008,
+ /* A new UTC date/time is available */
+ RDS_EVENT_UTCDATETIME = 0x0010,
+ /* A new local date/time is available */
+ RDS_EVENT_LOCDATETIME = 0x0020,
+ /* A radio text string was completed */
+ RDS_EVENT_LAST_RADIOTEXT = 0x0040,
+ /* Current Channel RF signal strength too weak, need do AF switch */
+ RDS_EVENT_AF = 0x0080,
+ /* An alternative frequency list is ready */
+ RDS_EVENT_AF_LIST = 0x0100,
+ /* An alternative frequency list is ready */
+ RDS_EVENT_AFON_LIST = 0x0200,
+ /* Other Network traffic announcement start */
+ RDS_EVENT_TAON = 0x0400,
+ /* Other Network traffic announcement finished. */
+ RDS_EVENT_TAON_OFF = 0x0800,
+ /* RDS Interrupt had arrived durint timer period */
+ RDS_EVENT_RDS = 0x2000,
+ /* RDS Interrupt not arrived durint timer period */
+ RDS_EVENT_NO_RDS = 0x4000,
+ /* Timer for RDS Bler Check. ---- BLER block error rate */
+ RDS_EVENT_RDS_TIMER = 0x8000
+};
+
+enum {
+ FM_I2S_ON = 0,
+ FM_I2S_OFF,
+ FM_I2S_STATE_ERR
+};
+
+enum {
+ FM_I2S_MASTER = 0,
+ FM_I2S_SLAVE,
+ FM_I2S_MODE_ERR
+};
+
+enum {
+ FM_I2S_32K = 0,
+ FM_I2S_44K,
+ FM_I2S_48K,
+ FM_I2S_SR_ERR
+};
+
+struct fm_i2s_setting {
+ int onoff;
+ int mode;
+ int sample;
+};
+
+enum {
+ FM_RX = 0,
+ FM_TX = 1
+};
+
+struct fm_i2s_info_t {
+ /* 0:FM_I2S_ON, 1:FM_I2S_OFF,2:error */
+ int status;
+ /* 0:FM_I2S_MASTER, 1:FM_I2S_SLAVE,2:error */
+ int mode;
+ /* 0:FM_I2S_32K:32000, 1:FM_I2S_44K:44100,2:FM_I2S_48K:48000,3:error */
+ int rate;
+};
+
+enum fm_audio_path_e {
+ FM_AUD_ANALOG = 0,
+ FM_AUD_I2S = 1,
+ FM_AUD_MRGIF = 2,
+ FM_AUD_ERR
+};
+
+enum fm_i2s_pad_sel_e {
+ FM_I2S_PAD_CONN = 0,
+ FM_I2S_PAD_IO = 1,
+ FM_I2S_PAD_ERR
+};
+
+struct fm_audio_info_t {
+ enum fm_audio_path_e aud_path;
+ struct fm_i2s_info_t i2s_info;
+ enum fm_i2s_pad_sel_e i2s_pad;
+};
+
+struct fm_cqi {
+ int ch;
+ int rssi;
+ int reserve;
+};
+
+struct fm_cqi_req {
+ uint16_t ch_num;
+ int buf_size;
+ char *cqi_buf;
+};
+
+struct fm_desense_check_t {
+ int freq;
+ int rssi;
+};
+
+struct fm_full_cqi_log_t {
+ /* lower band, Eg, 7600 -> 76.0Mhz */
+ uint16_t lower;
+ /* upper band, Eg, 10800 -> 108.0Mhz */
+ uint16_t upper;
+ /* 0x1: 50KHz, 0x2: 100Khz, 0x4: 200Khz */
+ int space;
+ /* repeat times */
+ int cycle;
+};
+
+struct fm_rx_data {
+ unsigned char *addr;
+ unsigned int len;
+ unsigned int fifo_id;
+ struct list_head entry;
+};
+
+struct fm_rds_handle {
+ /* is RDS on or off */
+ unsigned char rds_flag;
+ wait_queue_head_t rx_queue;
+ unsigned short new_data_flag;
+};
+
+struct fmdrv_ops {
+ struct completion completed;
+ unsigned int rcv_len;
+ void *read_buf;
+ void *tx_buf_p;
+ void *com_response;
+ void *seek_response;
+ unsigned int tx_len;
+ unsigned char write_buf[64];
+ unsigned char com_respbuf[12];
+ unsigned char seek_respbuf[12];
+ struct tasklet_struct rx_task;
+ struct tasklet_struct tx_task;
+ struct fm_rds_data rds_data;
+ spinlock_t rw_lock;
+ struct mutex mutex;
+ struct list_head rx_head;
+ struct completion commontask_completion;
+ struct completion seektask_completion;
+ struct completion *response_completion;
+ struct fm_rds_handle rds_han;
+};
+
+#define FM_IOC_MAGIC 0xf5
+#define FM_IOCTL_POWERUP _IOWR(FM_IOC_MAGIC, 0, struct fm_tune_parm*)
+#define FM_IOCTL_POWERDOWN _IOWR(FM_IOC_MAGIC, 1, int32_t*)
+#define FM_IOCTL_TUNE _IOWR(FM_IOC_MAGIC, 2, struct fm_tune_parm*)
+#define FM_IOCTL_SEEK _IOWR(FM_IOC_MAGIC, 3, struct fm_seek_parm*)
+#define FM_IOCTL_SETVOL _IOWR(FM_IOC_MAGIC, 4, uint32_t*)
+#define FM_IOCTL_GETVOL _IOWR(FM_IOC_MAGIC, 5, uint32_t*)
+#define FM_IOCTL_MUTE _IOWR(FM_IOC_MAGIC, 6, uint32_t*)
+#define FM_IOCTL_GETRSSI _IOWR(FM_IOC_MAGIC, 7, int32_t*)
+#define FM_IOCTL_SCAN _IOWR(FM_IOC_MAGIC, 8, struct fm_scan_parm*)
+#define FM_IOCTL_STOP_SCAN _IO(FM_IOC_MAGIC, 9)
+
+#define FM_IOCTL_GETCHIPID _IOWR(FM_IOC_MAGIC, 10, uint16_t*)
+#define FM_IOCTL_EM_TEST _IOWR(FM_IOC_MAGIC, 11, struct fm_em_parm*)
+
+#define FM_IOCTL_GETMONOSTERO _IOWR(FM_IOC_MAGIC, 13, uint16_t*)
+#define FM_IOCTL_GETCURPAMD _IOWR(FM_IOC_MAGIC, 14, uint16_t*)
+#define FM_IOCTL_GETGOODBCNT _IOWR(FM_IOC_MAGIC, 15, uint16_t*)
+#define FM_IOCTL_GETBADBNT _IOWR(FM_IOC_MAGIC, 16, uint16_t*)
+#define FM_IOCTL_GETBLERRATIO _IOWR(FM_IOC_MAGIC, 17, uint16_t*)
+
+#define FM_IOCTL_RDS_ONOFF _IOWR(FM_IOC_MAGIC, 18, uint16_t*)
+#define FM_IOCTL_RDS_SUPPORT _IOWR(FM_IOC_MAGIC, 19, int32_t*)
+
+#define FM_IOCTL_RDS_SIM_DATA _IOWR(FM_IOC_MAGIC, 23, uint32_t*)
+#define FM_IOCTL_IS_FM_POWERED_UP _IOWR(FM_IOC_MAGIC, 24, uint32_t*)
+
+#define FM_IOCTL_OVER_BT_ENABLE _IOWR(FM_IOC_MAGIC, 29, int32_t*)
+
+#define FM_IOCTL_ANA_SWITCH _IOWR(FM_IOC_MAGIC, 30, int32_t*)
+#define FM_IOCTL_GETCAPARRAY _IOWR(FM_IOC_MAGIC, 31, int32_t*)
+
+#define FM_IOCTL_I2S_SETTING _IOWR(FM_IOC_MAGIC, 33, struct fm_i2s_setting*)
+
+#define FM_IOCTL_RDS_GROUPCNT _IOWR(FM_IOC_MAGIC, 34, \
+ struct rds_group_cnt_req*)
+#define FM_IOCTL_RDS_GET_LOG _IOWR(FM_IOC_MAGIC, 35, struct rds_raw_data*)
+
+#define FM_IOCTL_SCAN_GETRSSI _IOWR(FM_IOC_MAGIC, 36, struct fm_rssi_req*)
+#define FM_IOCTL_SETMONOSTERO _IOWR(FM_IOC_MAGIC, 37, int32_t)
+#define FM_IOCTL_RDS_BC_RST _IOWR(FM_IOC_MAGIC, 38, int32_t*)
+#define FM_IOCTL_CQI_GET _IOWR(FM_IOC_MAGIC, 39, struct fm_cqi_req*)
+#define FM_IOCTL_GET_HW_INFO _IOWR(FM_IOC_MAGIC, 40, struct fm_hw_info*)
+#define FM_IOCTL_GET_I2S_INFO _IOWR(FM_IOC_MAGIC, 41, struct fm_i2s_info_t*)
+#define FM_IOCTL_IS_DESE_CHAN _IOWR(FM_IOC_MAGIC, 42, int32_t*)
+#define FM_IOCTL_TOP_RDWR _IOWR(FM_IOC_MAGIC, 43, struct fm_top_rw_parm*)
+#define FM_IOCTL_HOST_RDWR _IOWR(FM_IOC_MAGIC, 44, struct fm_host_rw_parm*)
+
+#define FM_IOCTL_PRE_SEARCH _IOWR(FM_IOC_MAGIC, 45, int32_t)
+#define FM_IOCTL_RESTORE_SEARCH _IOWR(FM_IOC_MAGIC, 46, int32_t)
+
+#define FM_IOCTL_SET_SEARCH_THRESHOLD _IOWR(FM_IOC_MAGIC, 47, \
+ fm_search_threshold_t*)
+
+#define FM_IOCTL_GET_AUDIO_INFO _IOWR(FM_IOC_MAGIC, 48, struct fm_audio_info_t*)
+
+#define FM_IOCTL_SCAN_NEW _IOWR(FM_IOC_MAGIC, 60, struct fm_scan_t*)
+#define FM_IOCTL_SEEK_NEW _IOWR(FM_IOC_MAGIC, 61, struct fm_seek_t*)
+#define FM_IOCTL_TUNE_NEW _IOWR(FM_IOC_MAGIC, 62, struct fm_tune_t*)
+
+#define FM_IOCTL_SOFT_MUTE_TUNE _IOWR(FM_IOC_MAGIC, 63, \
+ struct fm_softmute_tune_t*)
+#define FM_IOCTL_DESENSE_CHECK _IOWR(FM_IOC_MAGIC, 64, \
+ struct fm_desense_check_t*)
+
+
+/*IOCTL for SPRD SPECIAL */
+/*audio mode:0:mono, 1:stereo; 2:blending*/
+#define FM_IOCTL_SET_AUDIO_MODE _IOWR(FM_IOC_MAGIC, 0x47, int32_t*)
+#define FM_IOCTL_SET_REGION _IOWR(FM_IOC_MAGIC, 0x48, int32_t*)
+#define FM_IOCTL_SET_SCAN_STEP _IOWR(FM_IOC_MAGIC, 0x49, int32_t*)
+#define FM_IOCTL_CONFIG_DEEMPHASIS _IOWR(FM_IOC_MAGIC, 0x4A, int32_t*)
+#define FM_IOCTL_GET_AUDIO_MODE _IOWR(FM_IOC_MAGIC, 0x4B, int32_t*)
+#define FM_IOCTL_GET_CUR_BLER _IOWR(FM_IOC_MAGIC, 0x4C, int32_t*)
+#define FM_IOCTL_GET_SNR _IOWR(FM_IOC_MAGIC, 0x4D, int32_t*)
+#define FM_IOCTL_SOFTMUTE_ONOFF _IOWR(FM_IOC_MAGIC, 0x4E, int32_t*)
+/*Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI*/
+#define FM_IOCTL_SET_SEEK_CRITERIA _IOWR(FM_IOC_MAGIC, 0x4F, \
+ struct fm_seek_criteria_parm*)
+/*softmute ,blending ,snr_th*/
+#define FM_IOCTL_SET_AUDIO_THRESHOLD _IOWR(FM_IOC_MAGIC, 0x50, \
+ struct fm_audio_threshold_parm*)
+/*Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI*/
+#define FM_IOCTL_GET_SEEK_CRITERIA _IOWR(FM_IOC_MAGIC, 0x51, \
+ struct fm_seek_criteria_parm*)
+/*softmute ,blending ,snr_th*/
+#define FM_IOCTL_GET_AUDIO_THRESHOLD _IOWR(FM_IOC_MAGIC, 0x52, \
+ struct fm_audio_threshold_parm*)
+#define FM_IOCTL_RW_REG _IOWR(FM_IOC_MAGIC, 0xC, struct fm_reg_ctl_parm*)
+#define FM_IOCTL_AF_ONOFF _IOWR(FM_IOC_MAGIC, 0x53, uint16_t*)
+
+/* IOCTL for EM */
+#define FM_IOCTL_FULL_CQI_LOG _IOWR(FM_IOC_MAGIC, 70, \
+ struct fm_full_cqi_log_t *)
+
+#define FM_IOCTL_DUMP_REG _IO(FM_IOC_MAGIC, 0xFF)
+
+#define MAX_FM_FREQ 1080
+#define MIN_FM_FREQ 875
+
+#define FM_CTL_STI_MODE_NORMAL 0x0
+#define FM_CTL_STI_MODE_SEEK 0x1
+#define FM_CTL_STI_MODE_TUNE 0x2
+
+#endif /* _FM_DRV_H */
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_main.c b/drivers/misc/sprd-wcn/radio/fmdrv_main.c
new file mode 100644
index 0000000..c48b534
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv_main.c
@@ -0,0 +1,1245 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#include "fmdrv.h"
+#include "fmdrv_main.h"
+#include "fmdrv_ops.h"
+#include "fmdrv_rds_parser.h"
+
+#ifdef CONFIG_OF
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#endif
+
+#define FM_CHANNEL_WRITE 5
+#define FM_CHANNEL_READ 10
+#define FM_WRITE_SIZE (64)
+#define FM_READ_SIZE (128)
+#define FM_TYPE 1
+#define FM_SUBTYPE0 0
+#define FM_SUBTYPE1 1
+#define FM_SUBTYPE2 2
+#define FM_SUBTYPE3 3
+
+#define HCI_GRP_VENDOR_SPECIFIC 0x3F
+#define FM_SPRD_OP_CODE 0x008C
+#define hci_opcode_pack(ogf, ocf) \
+ ((unsigned short) ((ocf & 0x03ff) | (ogf << 10)))
+#define HCI_EV_CMD_COMPLETE 0x0e
+#define HCI_VS_EVENT 0xFF
+
+#define SEEKFORMAT "rssi_th =%d,snr_th =%d,freq_offset_th =%d," \
+ "pilot_power_th= %d,noise_power_th=%d"
+#define AUDIOFORMAT "hbound=%d,lbound =%d,power_th =%d," \
+ "phyt= %d,snr_th=%d"
+bool read_flag;
+struct fmdrv_ops *fmdev;
+static struct fm_rds_data *g_rds_data_string;
+
+/* for driver test */
+#define RX_NUM 100
+static unsigned char *buf_addr;
+static char a[RX_NUM] = {1, 2, 3, 4, 5};
+static unsigned char r1[11] = {0x04, 0x0e, 0x08, 0x01, 0x8c, 0xfc,
+ 0x00, 0xa1, 0x23, 0x12, 0x2A};
+static unsigned char r2[9] = {0x04, 0xFF, 0x6, 0x30, 0x00, 0x12, 0x13,
+ 0xb4, 0x23};
+static unsigned int (*rx_cb)(void *addr, unsigned int len,
+ unsigned int fifo_id);
+static unsigned int (*tx_cb)(void *addr);
+static struct timer_list test_timer;
+
+static void sdiom_register_pt_rx_process(unsigned int type,
+ unsigned int subtype,
+ void *func)
+{
+ rx_cb = func;
+}
+
+static void sdiom_register_pt_tx_release(unsigned int type,
+ unsigned int subtype,
+ void *func)
+{
+ tx_cb = func;
+}
+
+static unsigned int sdiom_pt_write(void *buf, unsigned int len,
+ int type, int subtype)
+{
+ int i = 0;
+
+ buf_addr = buf;
+ pr_info("fmdrv sdiom_pt_write len is %d\n", len);
+ for (i = 0; i < len; i++)
+ pr_info("fmdrv send data is %x\n", *(buf_addr+i));
+
+ mod_timer(&test_timer, jiffies + msecs_to_jiffies(30));
+
+ return 0;
+}
+
+unsigned int sdiom_pt_read_release(unsigned int fifo_id)
+{
+ return 0;
+}
+
+int start_marlin(int type)
+{
+ return 0;
+}
+
+int stop_marlin(int type)
+{
+ return 0;
+}
+
+static void timer_cb(unsigned long data)
+{
+ rx_cb(r1, 11, 0);
+ if (*(buf_addr+4) == 0x04) {
+ mdelay(100);
+ rx_cb(r2, 9, 0);
+ }
+}
+
+static void test_init(void)
+{
+ int i;
+
+ for (i = 0; i < RX_NUM; i++)
+ a[i] = i;
+}
+
+static int fm_send_cmd(unsigned char subcmd, void *payload,
+ int payload_len)
+{
+ unsigned char *cmd_buf;
+ struct fm_cmd_hdr *cmd_hdr;
+ int size;
+ int ret = 0;
+
+ size = sizeof(struct fm_cmd_hdr) +
+ ((payload == NULL) ? 0 : payload_len);
+
+ cmd_buf = kmalloc(size, GFP_KERNEL);
+ if (!cmd_buf)
+ return -ENOMEM;
+
+ /* Fill command information */
+ cmd_hdr = (struct fm_cmd_hdr *)cmd_buf;
+ cmd_hdr->header = 0x01;
+ cmd_hdr->opcode = hci_opcode_pack(HCI_GRP_VENDOR_SPECIFIC,
+ FM_SPRD_OP_CODE);
+ cmd_hdr->len = ((payload == NULL) ? 0 : payload_len) + 1;
+ cmd_hdr->fm_subcmd = subcmd;
+
+ if (payload != NULL)
+ memcpy(cmd_buf + sizeof(struct fm_cmd_hdr),
+ payload, payload_len);
+ fmdev->tx_buf_p = cmd_buf;
+ fmdev->tx_len = size;
+
+ ret = sdiom_pt_write(fmdev->tx_buf_p, size, FM_TYPE, FM_SUBTYPE0);
+ if (ret != 0) {
+ pr_err("fmdrv write cmd to sdiom fail Error number=%d\n", ret);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int fm_write_cmd(unsigned char subcmd, void *payload,
+ unsigned char payload_len, void *response,
+ unsigned char *response_len)
+{
+ unsigned long timeleft;
+ int ret;
+
+ mutex_lock(&fmdev->mutex);
+ init_completion(&fmdev->commontask_completion);
+ ret = fm_send_cmd(subcmd, payload, payload_len);
+ if (ret < 0) {
+ mutex_unlock(&fmdev->mutex);
+ return ret;
+ }
+
+ timeleft = wait_for_completion_timeout(&fmdev->commontask_completion,
+ FM_DRV_TX_TIMEOUT);
+ if (!timeleft) {
+ pr_err("(fmdrv) %s(): Timeout(%d sec),didn't get fm SubCmd\n"
+ "0x%02X completion signal from RX tasklet\n",
+ __func__, jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000, subcmd);
+ mutex_unlock(&fmdev->mutex);
+ return -ETIMEDOUT;
+ }
+
+ mutex_unlock(&fmdev->mutex);
+ pr_debug("fmdrv wait command have complete\n");
+ /* 0:len; XX XX XX sttaus */
+ if ((fmdev->com_respbuf[4]) != 0) {
+ pr_err("(fmdrv) %s(): Response status not success for 0x%02X\n",
+ __func__, subcmd);
+ return -EFAULT;
+ }
+ pr_info("(fmdrv) %s(): Response status success for 0x%02X: %d\n",
+ __func__, subcmd, fmdev->com_respbuf[4]);
+ /* the event : 04 0e len 01 8C fc 00(status) rssi snr freq .p->len */
+ if (response != NULL && response_len != NULL)
+ memcpy(response, &(fmdev->com_respbuf[5]),
+ fmdev->com_respbuf[0]-4);
+
+ return 0;
+}
+
+static void receive_tasklet(unsigned long arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct fm_rx_data *rx = NULL;
+ /* the data from SDIO is event data */
+ unsigned char *pdata;
+
+ fmdev = (struct fmdrv_ops *)arg;
+ if (unlikely(!fmdev)) {
+ pr_err("fm_rx_task fmdev is NULL\n");
+ return;
+ }
+ pr_info("fm %s start running\n", __func__);
+ while (!list_empty(&fmdev->rx_head)) {
+ spin_lock_bh(&fmdev->rw_lock);
+
+ rx = list_first_entry_or_null(&fmdev->rx_head,
+ struct fm_rx_data, entry);
+ if (rx)
+ list_del(&rx->entry);
+
+ else {
+ spin_unlock_bh(&fmdev->rw_lock);
+ return;
+ }
+ pdata = rx->addr;
+
+ if ((*((rx->addr)+1)) == 0x0e) {
+ memcpy(fmdev->com_respbuf, pdata + 2, (*(pdata+2)) + 1);
+ pr_debug("fm RX before commontask_completion=0x%x\n",
+ fmdev->commontask_completion.done);
+ complete(&fmdev->commontask_completion);
+ pr_debug("fm RX after commontask_completion=0x%x\n",
+ fmdev->commontask_completion.done);
+ sdiom_pt_read_release(rx->fifo_id);
+ pr_info("fmdrv release fifo_id is %d\n", rx->fifo_id);
+ }
+
+ else if (((*((rx->addr)+1)) == 0xFF) &&
+ ((*((rx->addr)+3)) == 0x30)) {
+ memcpy(fmdev->seek_respbuf, pdata + 2,
+ (*(pdata+2)) + 1);
+ /*fmdev->seek_response = rx;*/
+ pr_debug("fm RX before seektask_completion=0x%x\n",
+ fmdev->seektask_completion.done);
+ complete(&fmdev->seektask_completion);
+ pr_debug("fm RX after seektask_completion=0x%x\n",
+ fmdev->seektask_completion.done);
+ sdiom_pt_read_release(rx->fifo_id);
+ pr_info("fmdrv release fifo_id is %d\n", rx->fifo_id);
+ }
+
+ else if (((*((rx->addr)+1)) == 0xFF) &&
+ ((*((rx->addr)+3)) == 0x00))
+ rds_parser(pdata + 4, 12, rx->fifo_id);
+ else {
+ pr_err("fmdrv error:unknown event !!!\n");
+ sdiom_pt_read_release(rx->fifo_id);
+ pr_info("fmdrv release fifo_id is %d\n", rx->fifo_id);
+ }
+
+ kfree(rx);
+ rx = NULL;
+ spin_unlock_bh(&fmdev->rw_lock);
+ }
+}
+
+ssize_t fm_read_rds_data(struct file *filp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ int timeout = -1;
+ int ret;
+
+ pr_info("(FM_RDS) fm start to read RDS data\n");
+
+ if (filp->f_flags & O_NONBLOCK) {
+ timeout = 0;
+ pr_err("fm_read_rds_data NON BLOCK!!!\n");
+ return -EWOULDBLOCK;
+ }
+
+ if (timeout < 0) {
+ /* wait forever */
+ ret = wait_event_interruptible((fmdev->rds_han.rx_queue),
+ ((fmdev->rds_han.new_data_flag) == 1));
+ if (ret) {
+ pr_err("(FM RDS)wait_event_interruptible ret=%d\n",
+ ret);
+ return -EINTR;
+ }
+ }
+
+ fmdev->rds_data.rt_data.textlength =
+ strlen(fmdev->rds_data.rt_data.textdata[3]);
+ pr_info("fm RT len is %d\n", fmdev->rds_data.rt_data.textlength);
+ if (copy_to_user(buf, &(fmdev->rds_data), sizeof(fmdev->rds_data))) {
+ pr_info("fm_read_rds_data ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("(fm drs) fm event is %x\n", fmdev->rds_data.event_status);
+ fmdev->rds_data.event_status = 0;
+
+ pr_info("fmevent_status=%x\n", fmdev->rds_data.event_status);
+ pr_info("PS=%s\n", fmdev->rds_data.ps_data.PS[3]);
+ pr_info("fm_read_rds_data end....\n");
+
+ return sizeof(fmdev->rds_data);
+}
+
+void parse_at_fm_cmd(unsigned int *freq_found)
+{
+ int comma_cou = 0;
+ int i = 0;
+ int cmdstart = 0;
+ int len = 0;
+ char *cur_ptr;
+ char num_str[6] = {0};
+ int result = 0;
+
+ cur_ptr = fmdev->read_buf;
+ read_flag = 0;
+ for (i = 0; i < 32 && cur_ptr[i] != '\0'; i++) {
+ if (cur_ptr[i] == ',')
+ comma_cou++;
+ if (comma_cou == 3) {
+ comma_cou = 0;
+ cmdstart = i;
+ }
+ }
+ for (i = 0, cmdstart++; cmdstart < 32 && cur_ptr[cmdstart] != '\0'
+ && cur_ptr[cmdstart] != ','; i++, cmdstart++) {
+ if (cur_ptr[cmdstart] >= '0' && cur_ptr[cmdstart] <= '9')
+ num_str[i] = cur_ptr[cmdstart];
+ else if (cur_ptr[cmdstart] == ' ')
+ break;
+ }
+ len = strlen(num_str);
+ cur_ptr = num_str;
+ result = cur_ptr[0] - '0';
+ for (i = 1; i < len; i++)
+ result = result * 10 + cur_ptr[i] - '0';
+ *freq_found = result;
+ pr_info("fm seek event have come freq=%d\n", result);
+}
+
+int fm_open(struct inode *inode, struct file *filep)
+{
+ pr_info("start open SPRD fm module...\n");
+
+ return 0;
+}
+
+void fm_sdio_read(void)
+{
+ memset(fmdev->read_buf, 0, FM_READ_SIZE);
+ if (fmdev->rcv_len <= 0) {
+ pr_err("FM_CHANNEL_READ len err\n");
+ return;
+ }
+ if (fmdev->rcv_len > FM_READ_SIZE)
+ pr_err("The read data len:%d, beyond max read:%d",
+ fmdev->rcv_len, FM_READ_SIZE);
+ pr_info("* fmdev->read_buf: %s *\n", (char *)fmdev->read_buf);
+}
+
+int fm_sdio_write(unsigned char *buffer, unsigned int size)
+{
+ printk_ratelimited("%s size: %d\n", __func__, size);
+
+ return size;
+}
+
+int fm_sdio_init(void)
+{
+ return 0;
+}
+
+unsigned int fm_rx_cback(void *addr, unsigned int len, unsigned int fifo_id)
+{
+ unsigned char *buf;
+
+ buf = (unsigned char *)addr;
+
+ if (fmdev != NULL) {
+ struct fm_rx_data *rx =
+ kmalloc(sizeof(struct fm_rx_data), GFP_KERNEL);
+ if (!rx) {
+ pr_err("(fmdrv): %s(): No memory to create fm rx buf\n",
+ __func__);
+ sdiom_pt_read_release(fifo_id);
+ return -ENOMEM;
+ }
+ rx->addr = (unsigned char *)addr;
+ rx->len = len;
+ rx->fifo_id = fifo_id;
+ spin_lock_bh(&fmdev->rw_lock);
+ list_add_tail(&rx->entry, &fmdev->rx_head);
+ spin_unlock_bh(&fmdev->rw_lock);
+ pr_debug("(fmdrv) %s(): tasklet_schedule start\n", __func__);
+ tasklet_schedule(&fmdev->rx_task);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fm_rx_cback);
+
+void fm_tx_cback(void *tx_buff)
+{
+ if (tx_buff != NULL)
+ kfree(tx_buff);
+}
+EXPORT_SYMBOL_GPL(fm_tx_cback);
+
+int fm_write(unsigned char *array, unsigned char len)
+{
+ unsigned long timeleft;
+ int cnt = 0;
+
+ cnt = 0;
+ /* len = strlen(array); */
+ fm_sdio_write(array, len);
+
+ timeleft = wait_for_completion_timeout(&fmdev->completed,
+ FM_DRV_TX_TIMEOUT);
+ if (!timeleft) {
+ pr_err("Timeout, %d\n", ETIMEDOUT);
+ return -ETIMEDOUT;
+
+ }
+
+ pr_debug("success!\n");
+
+ return 0;
+}
+
+int fm_powerup(void *arg)
+{
+ struct fm_tune_parm parm;
+ unsigned short payload;
+ int ret = -1;
+
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_err("fm powerup 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+
+ if (start_marlin(MARLIN_FM)) {
+ pr_err("marlin2 chip %s failed\n", __func__);
+ return -ENODEV;
+ }
+
+ parm.freq *= 10;
+ pr_info("fm ioctl power up freq= %d\n", parm.freq);
+ payload = parm.freq;
+ ret = fm_write_cmd(FM_POWERUP_CMD, &payload,
+ sizeof(payload), NULL, NULL);
+ if (ret < 0)
+ pr_err("(fmdrv) %s FM write pwrup cmd status failed %d\n",
+ __func__, ret);
+
+ return ret;
+}
+
+int fm_powerdown(void)
+{
+ int ret = -EINVAL;
+ unsigned char payload = FM_OFF;
+
+ fmdev->rds_han.new_data_flag = 1;
+ wake_up_interruptible(&fmdev->rds_han.rx_queue);
+ ret = fm_write_cmd(FM_POWERDOWN_CMD, &payload, sizeof(payload),
+ NULL, NULL);
+ if (ret < 0)
+ pr_err("(fmdrv) %s FM write pwrdown cmd status failed %d\n",
+ __func__, ret);
+
+ return ret;
+}
+
+int fm_tune(void *arg)
+{ struct fm_tune_parm parm;
+ int ret = 0;
+ unsigned char respond_buf[4], respond_len;
+ unsigned short freq;
+
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_info("fm tune 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ parm.freq *= 10;
+ pr_debug("fm ioctl tune freq = %d\n", parm.freq);
+ ret = fm_write_cmd(FM_TUNE_CMD, &parm.freq, sizeof(parm.freq),
+ respond_buf, &respond_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write tune cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+ freq = respond_buf[2] + (respond_buf[3] << 8);
+ pr_debug("(fmdrv) fm tune have finshed!!status =0,RSSI=%d\n"
+ "(fmdrv) SNR=%d,freq=%d\n", respond_buf[0], respond_buf[1],
+ freq);
+
+ return ret;
+}
+
+/*
+ * seek cmd :01 8C FC 04(length) 04 freq(16bit) seekdir(8bit)
+ * payload == freq,seekdir
+ * seek event:status,RSSI,SNR,Freq
+ */
+int fm_seek(void *arg)
+{ struct fm_seek_parm parm;
+ int ret = 0;
+ unsigned char payload[3];
+ unsigned char respond_buf[5];
+ unsigned long timeleft;
+
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_info("fm seek 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ parm.freq *= 10;
+ payload[0] = (parm.freq & 0xFF);
+ payload[1] = (parm.freq >> 8);
+ payload[2] = parm.seekdir;
+ pr_info("fm ioctl seek freq=%d,dir =%d\n", parm.freq, parm.seekdir);
+ ret = fm_write_cmd(FM_SEEK_CMD, payload, sizeof(payload), NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write seek cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+ init_completion(&fmdev->seektask_completion);
+ timeleft = wait_for_completion_timeout(&fmdev->seektask_completion,
+ FM_DRV_RX_SEEK_TIMEOUT);
+ if (!timeleft) {
+ pr_err("(fmdrv) %s(): Timeout(%d sec),didn't get fm seek end !\n",
+ __func__, jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000);
+ /* -110 */
+ return -ETIMEDOUT;
+ }
+
+ memcpy(respond_buf, &(fmdev->seek_respbuf[2]),
+ fmdev->seek_respbuf[0] - 1);
+
+ parm.freq = respond_buf[3] + (respond_buf[4] << 8);
+ parm.freq /= 10;
+ pr_info("(fmdrv) fm seek have finshed!!status = %d, RSSI=%d\n"
+ "(fmdrv) fm seek SNR=%d, freq=%d\n", respond_buf[0],
+ respond_buf[1], respond_buf[2], parm.freq);
+ /* pass the value to user space */
+ if (copy_to_user(arg, &parm, sizeof(parm)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+/*
+ * mute cmd :01 8C FC 02(length) 02 mute(8bit)
+ * mute event:status,ismute
+ */
+int fm_mute(void *arg)
+{
+ unsigned char mute = 0;
+ int ret = -1;
+
+ if (copy_from_user(&mute, arg, sizeof(mute))) {
+ pr_err("fm mute 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+
+ if (mute == 1)
+ pr_info("fm ioctl mute\n");
+ else if (mute == 0)
+ pr_info("fm ioctl unmute\n");
+ else
+ pr_info("fm ioctl unknown cmd mute\n");
+
+ ret = fm_write_cmd(FM_MUTE_CMD, &mute,
+ sizeof(mute), NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write mute cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_set_volume(void *arg)
+{
+ unsigned char vol;
+ int ret = 0;
+
+ if (copy_from_user(&vol, arg, sizeof(vol))) {
+ pr_err("fm set volume 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl set_volume =%d\n", vol);
+ ret = fm_write_cmd(FM_SET_VOLUME_CMD, &vol, sizeof(vol),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM set volume status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_get_volume(void *arg)
+{
+ unsigned char payload = 0;
+ unsigned char res_len;
+ int volume;
+ unsigned char resp_buf[1];
+ int ret = -1;
+
+ pr_info("fm ioctl get volume =0x%x\n", volume);
+ ret = fm_write_cmd(FM_GET_VOLUME_CMD, &payload, sizeof(payload),
+ &resp_buf[0], &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write get volime cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ volume = (int)resp_buf[0];
+ if (copy_to_user(arg, &volume, sizeof(volume)))
+ ret = -EFAULT;
+
+ return ret;
+
+}
+
+int fm_stop_scan(void *arg)
+{
+ int ret = -EINVAL;
+
+ pr_info("fm ioctl stop scan\n");
+ ret = fm_write_cmd(FM_SEARCH_ABORT, NULL, 0,
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write stop scan cmd status failed %d\n",
+ __func__, ret);
+
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_scan_all(void *arg)
+{
+ struct fm_scan_all_parm parm;
+ int ret = 0;
+ unsigned char respond_len;
+ struct fm_scan_all_parm respond_buf;
+
+
+ pr_info("fm ioctl scan all\n");
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_err("fm search all 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+
+ ret = fm_write_cmd(FM_SCAN_ALL_CMD, &parm, sizeof(parm),
+ &respond_buf, &respond_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write scan all cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+ if (copy_to_user(arg, &parm, sizeof(parm)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+int fm_rw_reg(void *arg)
+{
+ struct fm_reg_ctl_parm parm;
+ int ret = 0;
+ unsigned char respond_len;
+
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_err("fm read and write register 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl read write reg = %d\n", parm.rw_flag);
+ ret = fm_write_cmd(FM_READ_WRITE_REG_CMD, &parm, sizeof(parm),
+ &parm, &respond_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write register cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+ if (copy_to_user(arg, &parm, sizeof(parm)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+int fm_get_monostero(void *arg)
+{
+ return 0;
+}
+
+/* audio mode: 0:None 1: mono 2:steron */
+int fm_set_audio_mode(void *arg)
+{
+ unsigned char mode;
+ int ret = 0;
+
+ if (copy_from_user(&mode, arg, sizeof(mode))) {
+ pr_err("fm set audio mode 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl set audio mode =%d\n", mode);
+ ret = fm_write_cmd(FM_SET_AUDIO_MODE, &mode, sizeof(mode),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM set audio mode status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_set_region(void *arg)
+{
+ unsigned char region;
+ int ret = 0;
+
+ if (copy_from_user(&region, arg, sizeof(region))) {
+ pr_err("fm set region 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl set region =%d\n", region);
+ ret = fm_write_cmd(FM_SET_REGION, &region, sizeof(region),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM set region status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_set_scan_step(void *arg)
+{
+ unsigned char step;
+ int ret = 0;
+
+ if (copy_from_user(&step, arg, sizeof(step))) {
+ pr_err("fm set scan step 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl set scan step =%d\n", step);
+ ret = fm_write_cmd(FM_SET_SCAN_STEP, &step, sizeof(step),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM set scan step status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_config_deemphasis(void *arg)
+{
+ unsigned char dp;
+ int ret = 0;
+
+ if (copy_from_user(&dp, arg, sizeof(dp))) {
+ pr_err("fm config_deemphasis 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl config_deemphasis =%d\n", dp);
+ ret = fm_write_cmd(FM_CONFIG_DEEMPHASIS, &dp, sizeof(dp),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM config_deemphasis status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_get_audio_mode(void *arg)
+{
+ unsigned char res_len;
+ int audio_mode;
+ unsigned char resp_buf[2];
+ int ret = -1;
+
+ pr_info("fm ioctl get audio mode\n");
+ ret = fm_write_cmd(FM_GET_AUDIO_MODE, NULL, 0,
+ &resp_buf[0], &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM get audio mode cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ audio_mode = (int)resp_buf[1];
+ if (copy_to_user(arg, &audio_mode, sizeof(audio_mode)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+int fm_get_current_bler(void *arg)
+{
+ unsigned char res_len;
+ int BLER;
+ unsigned char resp_buf[1];
+ int ret = -1;
+
+ pr_info("fm ioctl get current BLER\n");
+ ret = fm_write_cmd(DM_GET_CUR_BLER_CMD, NULL, 0,
+ &resp_buf[0], &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM get BLER cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ BLER = (int)resp_buf[0];
+ if (copy_to_user(arg, &BLER, sizeof(BLER)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+int fm_get_cur_snr(void *arg)
+{
+ unsigned char res_len;
+ int SNR;
+ unsigned char resp_buf[1];
+ int ret = -1;
+
+ pr_info("fm ioctl get current SNR\n");
+ ret = fm_write_cmd(FM_GET_SNR_CMD, NULL, 0,
+ &resp_buf[0], &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM get SNR cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ SNR = (int)resp_buf[0];
+ if (copy_to_user(arg, &SNR, sizeof(SNR)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+int fm_softmute_onoff(void *arg)
+{
+ unsigned char softmute_on;
+ int ret = 0;
+ unsigned char payload;
+
+ if (copy_from_user(&softmute_on, arg, sizeof(softmute_on))) {
+ pr_err("fm softmute_onoff 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ if (softmute_on == 0)
+ pr_info("fm ioctl softmute OFF\n");
+ else if (softmute_on == 1)
+ pr_info("fm ioctl softmute ON\n");
+ else
+ pr_info("fm ioctl unknown softmute\n");
+ payload = softmute_on;
+ ret = fm_write_cmd(FM_SOFTMUTE_ONOFF_CMD, &payload,
+ sizeof(payload), NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write softmute onoff cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_set_seek_criteria(void *arg)
+{
+ struct fm_seek_criteria_parm parm;
+ int ret = 0;
+
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_err("fm set_seek_criteria 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+
+ pr_info("fm ioctl set_seek_criteria "SEEKFORMAT"\n", parm.rssi_th,
+ parm.snr_th, parm.freq_offset_th,
+ parm.pilot_power_th, parm.noise_power_th);
+ ret = fm_write_cmd(FM_SET_SEEK_CRITERIA_CMD, &parm, sizeof(parm),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM set seek criteria cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * 1. soft_mute---soft mute parameters
+ * hbound >= lbound;
+ * hbound : valid range is 402 - 442(-70dbm ~ -110 dbm)
+ * lbound: valid range is 402 - 442(-70dbm ~ -110 dbm)
+ * Example
+ * lbound 422(-90dbm) hbound 427(-85dbm)
+ * Inpwr < -85dbm, enable softmute
+ * Inpwr > -90dbm ,disable softmute
+ *
+ * 2. blend----stereo/mono blend threshold
+ * power_th: the signal intensity,
+ * valid range 402~432(Mean:-80dbm~-110dbm)
+ * default value is 442
+ * phyt: Retardation coefficient valid range is 0~ 7; default value is 5
+ * Example:
+ * Power_th 422(-90dbm), Hyst 2
+ * inpwr< power_threshold- hyst\uff08420 mean-92dbm), switch mono
+ * inpwr>power_threshold+hyst (424 mean -88dbm), switch stereo
+ * 3. SNR_TH
+ */
+int fm_set_audio_threshold(void *arg)
+{
+ struct fm_audio_threshold_parm parm;
+ int ret = 0;
+
+ if (copy_from_user(&parm, arg, sizeof(parm))) {
+ pr_err("fm set_audio_threshold 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+
+ pr_info("fm ioctl set_audio_threshold" AUDIOFORMAT"\n",
+ parm.hbound, parm.lbound,
+ parm.power_th, parm.phyt, parm.snr_th);
+ ret = fm_write_cmd(FM_SET_AUDIO_THRESHOLD_CMD, &parm, sizeof(parm),
+ NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM set audio threshold cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_get_seek_criteria(void *arg)
+{
+
+ struct fm_seek_criteria_parm parm;
+ unsigned char res_len;
+
+ int ret = -1;
+
+ pr_info("fm ioctl get_seek_criteria\n");
+ ret = fm_write_cmd(FM_GET_SEEK_CRITERIA_CMD, NULL, 0,
+ &parm, &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write get seek_criteria cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (copy_to_user(arg, &parm, sizeof(parm)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+int fm_get_audio_threshold(void *arg)
+{
+ struct fm_audio_threshold_parm parm;
+ unsigned char res_len;
+ int ret = -1;
+
+ pr_info("fm ioctl get_audio_threshold\n");
+ ret = fm_write_cmd(FM_GET_AUDIO_THRESHOLD_CMD, NULL, 0,
+ &parm, &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write get audio_thresholdi cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (copy_to_user(arg, &parm, sizeof(parm)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+
+int fm_getrssi(void *arg)
+{
+ unsigned char payload = 0;
+ unsigned char res_len;
+ int rssi;
+ unsigned char resp_buf[1];
+ int ret = -1;
+
+ ret = fm_write_cmd(FM_GET_RSSI_CMD, &payload, sizeof(payload),
+ &resp_buf[0], &res_len);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write getrssi cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ rssi = (int)resp_buf[0];
+ if (copy_to_user(arg, &rssi, sizeof(rssi)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+struct fm_rds_data *get_rds_data(void)
+{
+ pr_info("fm get rds data\n");
+
+ return g_rds_data_string;
+}
+
+/*
+ * rdsonoff cmd :01 8C FC 03(length) 06 rdson(8bit) afon(8bit)
+ * rdsonoff event:status,rdson,afon
+ */
+int fm_rds_onoff(void *arg)
+{
+ unsigned char rds_on, af_on;
+ int ret = 0;
+ unsigned char payload[2];
+
+ if (copy_from_user(&rds_on, arg, sizeof(rds_on))) {
+ pr_err("fm rds_onoff 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ if (rds_on == 0) {
+ fmdev->rds_han.new_data_flag = 1;
+ memset(&fmdev->rds_data, 0, sizeof(fmdev->rds_data));
+ wake_up_interruptible(&fmdev->rds_han.rx_queue);
+ pr_info("fm ioctl RDS OFF\n");
+ } else if (rds_on == 1) {
+ fmdev->rds_han.new_data_flag = 0;
+ pr_info("fm ioctl RDS ON\n");
+ } else
+ pr_info("fm ioctl unknown RDS\n");
+ payload[0] = rds_on;
+ payload[1] = rds_on;
+ af_on = rds_on;
+ pr_debug("fm cmd: %d,%d,%d\n", FM_SET_RDS_MODE, rds_on, af_on);
+ ret = fm_write_cmd(FM_SET_RDS_MODE, payload,
+ sizeof(payload), NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write rds mode cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int fm_ana_switch(void *arg)
+{
+ int antenna;
+ int ret = 0;
+ unsigned char payload;
+
+ if (copy_from_user(&antenna, arg, sizeof(antenna))) {
+ pr_err("fm ana switch 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ pr_info("fm ioctl ana switch is %d\n", antenna);
+
+ payload = antenna;
+ ret = fm_write_cmd(FM_SET_ANA_SWITCH_CMD, &payload,
+ sizeof(payload), NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write ANA switch cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+
+}
+
+int fm_af_onoff(void *arg)
+{
+ unsigned char af_on;
+ int ret = 0;
+ unsigned char payload;
+
+ if (copy_from_user(&af_on, arg, sizeof(af_on))) {
+ pr_err("fm af_onoff 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ if (af_on == 0)
+ pr_info("fm ioctl AF OFF\n");
+ else if (af_on == 1)
+ pr_info("fm ioctl AF ON\n");
+ else
+ pr_info("fm ioctl unknown AF\n");
+ payload = af_on;
+ ret = fm_write_cmd(FM_SET_AF_ONOFF, &payload,
+ sizeof(payload), NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write af on off cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * get RSSI for every freq in AF list
+ * rdsonoff cmd :01 8C FC 01(length) 0D
+ * rdsonoff event:status,rdson,afon
+ *
+ */
+int fm_getcur_pamd(void *arg)
+{
+ unsigned char PAMD_LEN;
+ unsigned short PAMD;
+ int ret = -1;
+ unsigned char resp_buf[1];
+
+ ret = fm_write_cmd(FM_GET_CURPAMD, NULL, 0,
+ &resp_buf[0], &PAMD_LEN);
+ if (ret < 0) {
+ pr_err("(fmdrv) %s FM write getcur PAMD cmd status failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ PAMD = (unsigned short)resp_buf[0];
+ pr_debug("fm get PAMD =%d\n", PAMD);
+ if (copy_to_user(arg, &PAMD, sizeof(PAMD)))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+void set_rds_drv_data(struct fm_rds_data *fm_rds_info)
+{
+ g_rds_data_string = fm_rds_info;
+}
+
+void fm_rds_init(void)
+{
+ fmdev->rds_han.new_data_flag = 0;
+}
+
+int __init init_fm_driver(void)
+{
+ int ret = 0;
+ struct fm_rds_data *fm_rds_info;
+
+ fmdev = kzalloc(sizeof(struct fmdrv_ops), GFP_KERNEL);
+ if (!fmdev)
+ return -ENOMEM;
+
+ init_completion(&fmdev->completed);
+ init_completion(&fmdev->commontask_completion);
+ init_completion(&fmdev->seektask_completion);
+ spin_lock_init(&(fmdev->rw_lock));
+ mutex_init(&fmdev->mutex);
+ INIT_LIST_HEAD(&(fmdev->rx_head));
+
+ fmdev->read_buf = kzalloc(FM_READ_SIZE, GFP_KERNEL);
+ /* malloc mem for rds struct */
+ fm_rds_info = kzalloc(sizeof(struct fm_rds_data), GFP_KERNEL);
+ if (fm_rds_info == NULL) {
+
+ pr_err("fm can't allocate FM RDS buffer\n");
+ return ret;
+ }
+ set_rds_drv_data(fm_rds_info);
+
+ /* Register FM Tx and Rx callback */
+ sdiom_register_pt_rx_process(FM_TYPE, FM_SUBTYPE0, fm_rx_cback);
+ sdiom_register_pt_tx_release(FM_TYPE, FM_SUBTYPE0, fm_tx_cback);
+ /* retval = sdiodev_readchn_init(FM_CHANNEL_READ, fm_read, 0);*/
+ ret = fm_device_init_driver();
+
+ tasklet_init(&fmdev->rx_task, receive_tasklet, (unsigned long)fmdev);
+ /* RDS init */
+ fm_rds_init();
+ init_waitqueue_head(&fmdev->rds_han.rx_queue);
+
+ setup_timer(&test_timer, timer_cb, 0);
+ test_init();
+
+ return ret;
+}
+
+void __exit exit_fm_driver(void)
+{
+ fm_device_exit_driver();
+ tasklet_kill(&fmdev->tx_task);
+ tasklet_kill(&fmdev->rx_task);
+ kfree(fmdev->read_buf);
+ fmdev->read_buf = NULL;
+ kfree(fmdev);
+ fmdev = NULL;
+}
+
+module_init(init_fm_driver);
+module_exit(exit_fm_driver);
+MODULE_DESCRIPTION("SPREADTRUM SC2342 FM Radio driver");
+MODULE_AUTHOR("Songhe Wei<songhe.wei@xxxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(FM_VERSION);
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_main.h b/drivers/misc/sprd-wcn/radio/fmdrv_main.h
new file mode 100644
index 0000000..7dc3e39
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv_main.h
@@ -0,0 +1,117 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _FMDRV_MAIN_H
+#define _FMDRV_MAIN_H
+
+#include <linux/fs.h>
+
+#define FM_OFF 0x00
+#define FM_POWERUP_CMD 0x00
+#define FM_TUNE_CMD 0x01
+#define FM_MUTE_CMD 0x02
+#define FM_SCAN_ALL_CMD 0x03
+#define FM_SEEK_CMD 0x04
+#define FM_SEARCH_ABORT 0X05
+#define FM_SET_RDS_MODE 0x06
+#define FM_SET_RDS_TYPE 0x07
+/* audio mode:0:mono, 1:stereo; 2:blending */
+#define FM_SET_AUDIO_MODE 0x08
+#define FM_SET_AF_ONOFF 0x09
+/* #define FM_SET_AUDIO_PATH 0x09 */
+#define FM_SET_REGION 0x0A
+#define FM_SET_SCAN_STEP 0x0B
+#define FM_CONFIG_DEEMPHASIS 0x0C
+#define FM_GET_CURPAMD 0x0D
+/* audio mode:0:mono, 1:stereo; 2:blending */
+#define FM_GET_AUDIO_MODE 0x0E
+#define FM_GET_VOLUME_CMD 0x0F
+#define FM_SET_VOLUME_CMD 0x10
+#define DM_GET_CUR_BLER_CMD 0x11
+#define FM_POWERDOWN_CMD 0x12
+#define FM_GET_RSSI_CMD 0x13
+#define FM_GET_SNR_CMD 0x14
+#define FM_SOFTMUTE_ONOFF_CMD 0x15
+#define FM_SET_DEEMPHASIS_CMD 0x16
+/* Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI */
+#define FM_SET_SEEK_CRITERIA_CMD 0x17
+/* softmute ,blending ,snr_th */
+#define FM_SET_AUDIO_THRESHOLD_CMD 0x18
+/* Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI */
+#define FM_GET_SEEK_CRITERIA_CMD 0x19
+/* softmute ,blending ,snr_th */
+#define FM_GET_AUDIO_THRESHOLD_CMD 0x1A
+#define FM_SET_ANA_SWITCH_CMD 0x1B
+
+#define FM_READ_WRITE_REG_CMD 0x22
+
+extern struct fmdrv_ops *fmdev;
+
+int fm_open(struct inode *inode, struct file *filep);
+int fm_powerup(void *arg);
+int fm_powerdown(void);
+int fm_tune(void *arg);
+int fm_seek(void *arg);
+int fm_mute(void *arg);
+int fm_getrssi(void *arg);
+int fm_getcur_pamd(void *arg);
+int fm_rds_onoff(void *arg);
+int fm_ana_switch(void *arg);
+int fm_af_onoff(void *arg);
+int fm_set_volume(void *arg);
+int fm_get_volume(void *arg);
+int fm_stop_scan(void *arg);
+int fm_scan_all(void *arg);
+int fm_rw_reg(void *arg);
+int fm_get_monostero(void *arg);
+int fm_scan_all(void *arg);
+int fm_rw_reg(void *arg);
+int fm_stop_scan(void *arg);
+int fm_rw_reg(void *arg);
+int fm_get_monostero(void *arg);
+int fm_set_audio_mode(void *arg);
+int fm_set_region(void *arg);
+int fm_set_scan_step(void *arg);
+int fm_config_deemphasis(void *arg);
+int fm_get_audio_mode(void *arg);
+int fm_get_current_bler(void *arg);
+int fm_get_cur_snr(void *arg);
+int fm_softmute_onoff(void *arg);
+int fm_set_seek_criteria(void *arg);
+int fm_set_audio_threshold(void *arg);
+int fm_get_seek_criteria(void *arg);
+int fm_get_audio_threshold(void *arg);
+ssize_t fm_read_rds_data(struct file *filp, char __user *buf,
+ size_t count, loff_t *pos);
+int fm_sdio_write(unsigned char *buffer, unsigned int size);
+struct fm_rds_data *get_rds_data(void);
+int start_marlin(int type);
+int stop_marlin(int type);
+unsigned int sdiom_pt_read_release(unsigned int fifo_id);
+
+struct fm_cmd_hdr {
+ /* 01:cmd; 04:event */
+ unsigned char header;
+ /* vendor specific command 0xFC8C */
+ unsigned short opcode;
+ /* Number of bytes follows */
+ unsigned char len;
+ /* FM Sub Command */
+ unsigned char fm_subcmd;
+} __packed;
+
+struct fm_event_hdr {
+ /* 01:cmd; 04:event */
+ unsigned char header;
+ /* 0e:cmd complete event; FF:vendor specific event */
+ unsigned char id;
+ /* Number of bytes follows */
+ unsigned char len;
+} __packed;
+
+#endif /* _FMDRV_MAIN_H */
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_ops.c b/drivers/misc/sprd-wcn/radio/fmdrv_ops.c
new file mode 100644
index 0000000..bd3ec3f
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv_ops.c
@@ -0,0 +1,447 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/compat.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#ifdef CONFIG_OF
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#endif
+
+#include "fmdrv.h"
+#include "fmdrv_main.h"
+#include "fmdrv_ops.h"
+
+static long fm_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ long ret = 0;
+ u32 iarg = 0;
+
+ pr_debug("FM_IOCTL cmd: 0x%x.\n", cmd);
+ switch (cmd) {
+ case FM_IOCTL_POWERUP:
+ fm_powerup(argp);
+ ret = fm_tune(argp);
+ break;
+
+ case FM_IOCTL_POWERDOWN:
+ ret = fm_powerdown();
+ break;
+
+ case FM_IOCTL_TUNE:
+ ret = fm_tune(argp);
+ break;
+
+ case FM_IOCTL_SEEK:
+ ret = fm_seek(argp);
+ break;
+
+ case FM_IOCTL_SETVOL:
+ pr_info("fm ioctl set volume\n");
+ ret = fm_set_volume(argp);
+ break;
+
+ case FM_IOCTL_GETVOL:
+ pr_info("fm ioctl get volume\n");
+ ret = fm_get_volume(argp);
+ break;
+
+ case FM_IOCTL_MUTE:
+ ret = fm_mute(argp);
+ break;
+
+ case FM_IOCTL_GETRSSI:
+ pr_info("fm ioctl get RSSI\n");
+ ret = fm_getrssi(argp);
+ break;
+
+ case FM_IOCTL_SCAN:
+ pr_info("fm ioctl SCAN\n");
+ ret = fm_scan_all(argp);
+ break;
+
+ case FM_IOCTL_STOP_SCAN:
+ pr_info("fm ioctl STOP SCAN\n");
+ ret = fm_stop_scan(argp);
+ break;
+
+ case FM_IOCTL_GETCHIPID:
+ pr_info("fm ioctl GET chipID\n");
+ iarg = 0x2341;
+ if (copy_to_user(argp, &iarg, sizeof(iarg)))
+ ret = -EFAULT;
+ else
+ ret = 0;
+ break;
+
+ case FM_IOCTL_EM_TEST:
+ pr_info("fm ioctl EM_TEST\n");
+ ret = 0;
+ break;
+
+ case FM_IOCTL_RW_REG:
+ pr_info("fm ioctl RW_REG\n");
+ ret = fm_rw_reg(argp);
+ break;
+
+ case FM_IOCTL_GETMONOSTERO:
+ pr_info("fm ioctl GETMONOSTERO\n");
+ ret = fm_get_monostero(argp);
+ break;
+ case FM_IOCTL_GETCURPAMD:
+ pr_info("fm ioctl get PAMD\n");
+ ret = fm_getcur_pamd(argp);
+ break;
+
+ case FM_IOCTL_GETGOODBCNT:
+ case FM_IOCTL_GETBADBNT:
+ case FM_IOCTL_GETBLERRATIO:
+ case FM_IOCTL_RDS_SIM_DATA:
+ case FM_IOCTL_IS_FM_POWERED_UP:
+ case FM_IOCTL_OVER_BT_ENABLE:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_RDS_ONOFF:
+ pr_info("----RDS_ONOFF----");
+ ret = fm_rds_onoff(argp);
+ break;
+
+ case FM_IOCTL_RDS_SUPPORT:
+ pr_info("fm ioctl is RDS_SUPPORT\n");
+ ret = 0;
+ if (copy_from_user(&iarg, (void __user *)arg, sizeof(iarg))) {
+ pr_err("fm RDS support 's ret value is -eFAULT\n");
+ return -EFAULT;
+ }
+ iarg = FM_RDS_ENABLE;
+ if (copy_to_user((void __user *)arg, &iarg, sizeof(iarg)))
+ ret = -EFAULT;
+ break;
+
+ case FM_IOCTL_ANA_SWITCH:
+ ret = fm_ana_switch(argp);
+ break;
+
+ case FM_IOCTL_GETCAPARRAY:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_I2S_SETTING:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_RDS_GROUPCNT:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_RDS_GET_LOG:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_SCAN_GETRSSI:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_SETMONOSTERO:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_RDS_BC_RST:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_CQI_GET:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_GET_HW_INFO:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_GET_I2S_INFO:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_IS_DESE_CHAN:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_TOP_RDWR:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_HOST_RDWR:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_PRE_SEARCH:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_RESTORE_SEARCH:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_GET_AUDIO_INFO:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_SCAN_NEW:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_SEEK_NEW:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_TUNE_NEW:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_SOFT_MUTE_TUNE:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_DESENSE_CHECK:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_FULL_CQI_LOG:
+ ret = 0;
+ break;
+
+ case FM_IOCTL_SET_AUDIO_MODE:
+ ret = fm_set_audio_mode(argp);
+ break;
+
+ case FM_IOCTL_SET_REGION:
+ ret = fm_set_region(argp);
+ break;
+
+ case FM_IOCTL_SET_SCAN_STEP:
+ ret = fm_set_scan_step(argp);
+ break;
+
+ case FM_IOCTL_CONFIG_DEEMPHASIS:
+ ret = fm_config_deemphasis(argp);
+ break;
+
+ case FM_IOCTL_GET_AUDIO_MODE:
+ ret = fm_get_audio_mode(argp);
+ break;
+
+ case FM_IOCTL_GET_CUR_BLER:
+ ret = fm_get_current_bler(argp);
+ break;
+
+ case FM_IOCTL_GET_SNR:
+ ret = fm_get_cur_snr(argp);
+ break;
+
+ case FM_IOCTL_SOFTMUTE_ONOFF:
+ ret = fm_softmute_onoff(argp);
+ break;
+
+ case FM_IOCTL_SET_SEEK_CRITERIA:
+ ret = fm_set_seek_criteria(argp);
+ break;
+
+ case FM_IOCTL_SET_AUDIO_THRESHOLD:
+ ret = fm_set_audio_threshold(argp);
+ break;
+
+ case FM_IOCTL_GET_SEEK_CRITERIA:
+ ret = fm_get_seek_criteria(argp);
+ break;
+
+ case FM_IOCTL_GET_AUDIO_THRESHOLD:
+ ret = fm_get_audio_threshold(argp);
+ break;
+
+ case FM_IOCTL_AF_ONOFF:
+ ret = fm_af_onoff(argp);
+ break;
+
+ case FM_IOCTL_DUMP_REG:
+ ret = 0;
+ break;
+
+ default:
+ pr_info("Unknown FM IOCTL cmd=0x%x.\n", cmd);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int fm_release(struct inode *inode, struct file *filep)
+{
+ pr_info("fm_misc_release.\n");
+ fm_powerdown();
+ stop_marlin(MARLIN_FM);
+ wake_up_interruptible(&fmdev->rds_han.rx_queue);
+ fmdev->rds_han.new_data_flag = 1;
+
+ return 0;
+}
+
+#ifdef CONFIG_COMPAT
+static long fm_compat_ioctl(struct file *file,
+ unsigned int cmd, unsigned long data)
+{
+ pr_info("start_fm_compat_ioctl FM_IOCTL cmd: 0x%x.\n", cmd);
+ cmd = cmd & 0xFFF0FFFF;
+ cmd = cmd | 0x00080000;
+ pr_info("fm_compat_ioctl FM_IOCTL cmd: 0x%x.\n", cmd);
+ return fm_ioctl(file, cmd, (unsigned long)compat_ptr(data));
+}
+#endif
+
+const struct file_operations fm_misc_fops = {
+ .owner = THIS_MODULE,
+ .open = fm_open,
+ .read = fm_read_rds_data,
+ .unlocked_ioctl = fm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = fm_compat_ioctl,
+#endif
+ .release = fm_release,
+};
+
+struct miscdevice fm_misc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = FM_DEV_NAME,
+ .fops = &fm_misc_fops,
+};
+
+#ifdef CONFIG_OF
+
+static const struct of_device_id of_match_table_fm[] = {
+ { .compatible = "sprd,marlin2-fm", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_match_table_fm);
+#endif
+
+static int fm_probe(struct platform_device *pdev)
+{
+ int ret = -EINVAL;
+ char *ver_str = FM_VERSION;
+
+#ifdef CONFIG_OF
+ struct device_node *np;
+
+ np = pdev->dev.of_node;
+#endif
+
+ pr_info(" marlin2 FM driver\n");
+ pr_info(" Version: %s\n", ver_str);
+
+ ret = misc_register(&fm_misc_device);
+ if (ret < 0) {
+
+ pr_info("misc_register failed!\n");
+ return ret;
+ }
+
+ pr_info("fm_init success.\n");
+
+ return 0;
+}
+
+static int fm_remove(struct platform_device *pdev)
+{
+
+ pr_info("exit_fm_driver!\n");
+ misc_deregister(&fm_misc_device);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fm_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int fm_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fm_pmops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fm_suspend, fm_resume)
+};
+
+static struct platform_driver fm_driver = {
+ .driver = {
+ .name = "sprd-fm",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(of_match_table_fm),
+#endif
+ .pm = &fm_pmops,
+ },
+ .probe = fm_probe,
+ .remove = fm_remove,
+};
+
+#ifndef CONFIG_OF
+struct platform_device fm_device = {
+ .name = "sprd-fm",
+ .id = -1,
+};
+#endif
+
+int fm_device_init_driver(void)
+{
+ int ret;
+#ifndef CONFIG_OF
+ ret = platform_device_register(&fm_device);
+ if (ret) {
+ pr_info("fm: platform_device_register failed: %d\n", ret);
+ return ret;
+ }
+#endif
+ ret = platform_driver_register(&fm_driver);
+ if (ret) {
+#ifndef CONFIG_OF
+ platform_device_unregister(&fm_device);
+#endif
+ pr_info("fm: probe failed: %d\n", ret);
+ }
+ pr_info("fm: probe success: %d\n", ret);
+
+ return ret;
+}
+
+void fm_device_exit_driver(void)
+{
+ platform_driver_unregister(&fm_driver);
+
+}
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_ops.h b/drivers/misc/sprd-wcn/radio/fmdrv_ops.h
new file mode 100644
index 0000000..b3a019e
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv_ops.h
@@ -0,0 +1,17 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+
+#ifndef _FMDRV_OPS_H
+#define _FMDRV_OPS_H
+
+extern struct fmdrv_ops *fmdev;
+int fm_device_init_driver(void);
+void fm_device_exit_driver(void);
+
+#endif /* _FMDRV_OPS_H */
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
new file mode 100644
index 0000000..538b3b9
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
@@ -0,0 +1,753 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include "fmdrv.h"
+#include "fmdrv_main.h"
+#include "fmdrv_rds_parser.h"
+
+static struct fm_rds_data *g_rds_data_p;
+/* the next ps: index = 0 */
+static unsigned char flag_next = 1;
+void rds_parser_init(void)
+{
+ g_rds_data_p = get_rds_data();
+}
+
+void fmr_assert(unsigned short *a)
+{
+ if (a == NULL)
+ pr_info("%s,invalid pointer\n", __func__);
+}
+
+/*
+ * rds_event_set
+ * To set rds event, and user space can use this flag to juge
+ * which event happened
+ * If success return 0, else return error code
+ */
+static signed int rds_event_set(unsigned short *events, signed int event_mask)
+{
+ fmr_assert(events);
+ *events |= event_mask;
+ wake_up_interruptible(&fmdev->rds_han.rx_queue);
+ fmdev->rds_han.new_data_flag = 1;
+
+ return 0;
+}
+
+/*
+ * Group types which contain this information:
+ * TA(Traffic Program) code 0A 0B 14B 15B
+ */
+void rds_get_eon_ta(unsigned char *buf)
+{
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+ unsigned char data = *(buf + rds_data_unit_size + 2);
+ unsigned char ta_tp;
+ unsigned int pi_on;
+
+ if (*blk_4 == 0)
+ return;
+ /* bit3: TA ON bit4: TP ON */
+ ta_tp = (unsigned char)(((data & (1 << 4)) >> 4) | ((data & (1 << 3))
+ << 1));
+ bytes_to_short(pi_on, blk_4 + 1);
+ /* need add some code to adapter google upper layer here */
+}
+
+/*
+ * EON = Enhanced Other Networks information
+ * Group types which contain this information: EON : 14A
+ * variant code is in blockB low 4 bits
+ */
+void rds_get_eon(unsigned char *buf)
+{
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+ unsigned short pi_on;
+
+ if ((*blk_3 == 0) || (*blk_4 == 0))
+ return;
+ /* if the upper Layer true */
+ bytes_to_short(pi_on, blk_4 + 1);
+}
+
+/*
+ * PTYN = Programme TYpe Name
+ * From Group 10A, it's a 8 character description transmitted in two 10A group
+ * block 2 bit0 is PTYN segment address.
+ * block3 and block4 is PTYN text character
+ */
+void rds_get_ptyn(unsigned char *buf)
+{
+ unsigned char *blk_2 = buf + rds_data_unit_size;
+ unsigned char *blk_head[2];
+ unsigned char seg_addr = ((*(blk_2 + 2)) & 0x01);
+ unsigned char ptyn[4], i, step;
+ unsigned char *blkc = buf + 2 * rds_data_unit_size;
+ unsigned char *blkd = buf + 2 * rds_data_unit_size;
+
+ blk_head[0] = buf + 2 * rds_data_unit_size;
+ blk_head[1] = buf + 3 * rds_data_unit_size;
+ memcpy((void *)&ptyn[0], (void *)(blk_head[0] + 1), 2);
+ memcpy((void *)&ptyn[2], (void *)(blk_head[1] + 1), 2);
+ for (i = 0; i < 2; i++) {
+ step = i >> 1;
+ /* update seg_addr[0,1] if blockC/D is reliable data */
+ if ((*blkc == 1) && (*blkd == 1)) {
+ /* it's a new PTYN */
+ if (memcmp((void *)&ptyn[seg_addr * 4 + step], (void *)
+ (ptyn + step), 2) != 0)
+ memcpy((void *)&ptyn[seg_addr * 4 + step],
+ (void *)(ptyn + step), 2);
+ }
+ }
+}
+
+/*
+ * EWS = Coding of Emergency Warning Systems
+ * EWS inclued belows:
+ * unsigned char data_5b;
+ * unsigned short data_16b_1;
+ * unsigned short data_16b_2;
+ */
+void rds_get_ews(unsigned char *buf)
+{
+ unsigned char data_5b;
+ unsigned short data_16b_1;
+ unsigned short data_16b_2;
+ unsigned char *blk_2 = buf + rds_data_unit_size;
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+
+ data_5b = (unsigned char)((*(blk_2 + 2)) & 0x1F);
+ bytes_to_short(data_16b_1, (blk_3 + 1));
+ bytes_to_short(data_16b_2, (blk_4 + 1));
+}
+
+void rfd_get_rtplus(unsigned char *buf)
+{
+ unsigned char *blk_b = buf + rds_data_unit_size;
+ unsigned char *blk_c = buf + 2 * rds_data_unit_size;
+ unsigned char *blk_d = buf + 3 * rds_data_unit_size;
+ unsigned char content_type, s_marker, l_marker;
+ bool running;
+
+ running = ((*(blk_b + 2) & 0x08) != 0) ? 1 : 0;
+ if ((*blk_c == 1) && (*blk_b == 1)) {
+ content_type = ((*(blk_b + 2) & 0x07) << 3) + (*(blk_c + 1)
+ >> 5);
+ s_marker = (((*(blk_c + 1) & 0x1F) << 1) + (*(blk_c + 2)
+ >> 7));
+ l_marker = (((*(blk_c + 2)) & 0x7F) >> 1);
+ }
+ if ((*blk_c == 1) && (*blk_d == 1)) {
+ content_type = ((*(blk_c + 2) & 0x01) << 5) +
+ (*(blk_d + 1) >> 3);
+ s_marker = (*(blk_d + 2) >> 5) + ((*(blk_d + 1) & 0x07) << 3);
+ l_marker = (*(blk_d + 2) & 0x1f);
+ }
+}
+
+/* ODA = Open Data Applications */
+void rds_get_oda(unsigned char *buf)
+{
+ rfd_get_rtplus(buf);
+}
+
+/* TDC = Transparent Data Channel */
+void rds_get_tdc(unsigned char *buf, unsigned char version)
+{
+ /* 2nd block */
+ unsigned char *blk_b = buf + rds_data_unit_size;
+ /* 3rd block */
+ unsigned char *blk_c = buf + 2*rds_data_unit_size;
+ /* 4rd block */
+ unsigned char *blk_d = buf + 3*rds_data_unit_size;
+ unsigned char chnl_num, len, tdc_seg[4];
+ /* unrecoverable block 3,or ERROR in block 4, discard this group */
+ if ((*blk_b == 0) || (*blk_c == 0) || (*blk_d == 0))
+ return;
+
+ /* read TDChannel number */
+ chnl_num = *(blk_b + 2) & 0x1f;
+ if (version == grp_ver_a) {
+ memcpy(tdc_seg, blk_c + 1, 2);
+ len = 2;
+ }
+
+ memcpy(tdc_seg + len, blk_d + 1, 2);
+ len += 2;
+}
+
+/* CT = Programe Clock time */
+void rds_get_ct(unsigned char *buf)
+{
+ unsigned char *blk_2 = buf + rds_data_unit_size;
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+ unsigned char b3_1 = *(blk_3 + 1), b3_2 = *(blk_3 + 2);
+ unsigned char b4_1 = *(blk_4 + 1), b4_2 = *(blk_4 + 2);
+ unsigned int temp1, temp2;
+
+ unsigned int day = 0;
+ unsigned char hour, minute, sense, offset;
+
+ if ((*(blk_3) == 0) || (*(blk_4) == 0))
+ return;
+ temp1 = (unsigned int) ((b3_1 << 8) | b3_2);
+ temp2 = (unsigned int) (*(blk_2 + 2) & 0x03);
+ day = (temp2 << 15) | (temp1 >> 1);
+
+ temp1 = (unsigned int)(b3_2 & 0x01);
+ temp2 = (unsigned int)(b4_1 & 0xF0);
+ hour = (unsigned char)((temp1 << 4) | (temp2 >> 4));
+ minute = ((b4_1 & 0x0F) << 2) | ((b4_2 & 0xC0) >> 6);
+ sense = (b4_2 & 0x20) >> 5;
+ offset = b4_2 & 0x1F;
+ /* set RDS EVENT FLAG in here */
+ fmdev->rds_data.CT.day = day;
+ fmdev->rds_data.CT.hour = hour;
+ fmdev->rds_data.CT.minute = minute;
+ fmdev->rds_data.CT.local_time_offset_half_hour = offset;
+ fmdev->rds_data.CT.local_time_offset_signbit = sense;
+}
+
+void rds_get_oda_aid(unsigned char *buf)
+{
+}
+
+/*
+ * rt == Radio Text
+ * Group types which contain this information: 2A 2B
+ * 2A: address in block2 last 4bits, Text in block3 and block4
+ * 2B: address in block2 last 4bits, Text in block4(16bits)
+ */
+void rds_get_rt(unsigned char *buf, unsigned char grp_type)
+{
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+ unsigned char addr = ((*(buf + rds_data_unit_size + 2)) & 0x0F);
+ unsigned char text_flag = ((*(buf + rds_data_unit_size + 2)) & 0x10);
+
+ pr_info("RT Text A/B Flag is %d\n", text_flag);
+
+ /* add for RT not support two types*/
+ if (text_flag != 0)
+ return;
+ if (grp_type == 0x2A) {
+ if (*(blk_3 + 1) == 0x0d)
+ *(blk_3 + 1) = '\0';
+ if (*(blk_3 + 2) == 0x0d)
+ *(blk_3 + 2) = '\0';
+ if (*(blk_4 + 1) == 0x0d)
+ *(blk_4 + 1) = '\0';
+ if (*(blk_4 + 2) == 0x0d)
+ *(blk_4 + 2) = '\0';
+ fmdev->rds_data.rt_data.textdata[3][addr * 4] = *(blk_3 + 1);
+ fmdev->rds_data.rt_data.textdata[3][addr * 4 + 1] =
+ *(blk_3 + 2);
+ fmdev->rds_data.rt_data.textdata[3][addr * 4 + 2] =
+ *(blk_4 + 1);
+ fmdev->rds_data.rt_data.textdata[3][addr * 4 + 3] =
+ *(blk_4 + 2);
+ }
+ /* group type = 2B */
+ else {
+ if (*(blk_3 + 1) == 0x0d)
+ *(blk_3 + 1) = '\0';
+ if (*(blk_3 + 2) == 0x0d)
+ *(blk_3 + 2) = '\0';
+ fmdev->rds_data.rt_data.textdata[3][addr * 2] = *(blk_3 + 1);
+ fmdev->rds_data.rt_data.textdata[3][addr * 2 + 1] =
+ *(blk_3 + 2);
+ }
+ rds_event_set(&(fmdev->rds_data.event_status),
+ RDS_EVENT_LAST_RADIOTEXT);
+ pr_info("RT is %s\n", fmdev->rds_data.rt_data.textdata[3]);
+}
+
+/* PIN = Programme Item Number */
+
+void rds_get_pin(unsigned char *buf)
+{
+ struct RDS_PIN {
+ unsigned char day;
+ unsigned char hour;
+ unsigned char minute;
+ } rds_pin;
+
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+ unsigned char byte1 = *(blk_4 + 1), byte2 = *(blk_4 + 2);
+
+ if (*blk_4 == 0)
+ return;
+ rds_pin.day = ((byte1 & 0xF8) >> 3);
+ rds_pin.hour = (byte1 & 0x07) << 2 | ((byte2 & 0xC0) >> 6);
+ rds_pin.minute = (byte2 & 0x3F);
+}
+
+/*
+ * SLC = Slow Labelling codes from group 1A, block3
+ * LA 0 0 0 OPC ECC
+ */
+
+void rds_get_slc(unsigned char *buf)
+{
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+ unsigned char variant_code, slc_type, paging;
+ unsigned char ecc_code = 0;
+ unsigned short data;
+
+ if ((*blk_3) == 0)
+ return;
+ bytes_to_short(data, blk_3);
+ data &= 0x0FFF;
+ /* take bit12 ~ bit14 of block3 as variant code */
+ variant_code = ((*(blk_3 + 1) & 0x70) >> 4);
+ if ((variant_code == 0x04) || (variant_code == 0x05))
+ slc_type = 0x04;
+ else
+ slc_type = variant_code;
+ if (slc_type == 0) {
+ ecc_code = *(blk_3 + 2);
+ paging = (*(blk_3 + 1) & 0x0f);
+ }
+ fmdev->rds_data.extend_country_code = ecc_code;
+}
+
+/*
+ * Group types which contain this information: 0A 0B
+ * PS = Programme Service name
+ * block2 last 2bit stard for address, block4 16bits meaning ps.
+ */
+
+void rds_get_ps(unsigned char *buf)
+{
+ unsigned char *blk_2 = buf + rds_data_unit_size;
+ unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
+ unsigned char index = (unsigned char)((*(blk_2 + 2) & 0x03) * 2);
+
+ pr_info("PS start receive\n");
+ pr_info("blk2 =%d, blk4=%d\n", *blk_2, *blk_4);
+ if ((*blk_2) == 1) {
+ if ((flag_next == 0) && (index == 0)) {
+ memcpy(fmdev->rds_data.ps_data.PS[3],
+ fmdev->rds_data.ps_data.PS[2], 8);
+ pr_info("PS is %s\n", fmdev->rds_data.ps_data.PS[3]);
+ if (fmdev->rds_data.ps_data.PS[3] != NULL)
+ rds_event_set(&(fmdev->rds_data.event_status),
+ RDS_EVENT_PROGRAMNAME);
+ memset(fmdev->rds_data.ps_data.PS[2], 0x0, 8);
+ }
+ if (flag_next == 1)
+ flag_next = 0;
+
+ fmdev->rds_data.ps_data.addr_cnt = index;
+ fmdev->rds_data.ps_data.PS[2][index] = *(blk_4 + 1);
+ fmdev->rds_data.ps_data.PS[2][index + 1] = *(blk_4 + 2);
+ }
+ pr_info("the PS index is %x\n", index);
+ pr_info("The event is %x\n", fmdev->rds_data.event_status);
+ pr_info("The PS is %s\n", fmdev->rds_data.ps_data.PS[3]);
+ pr_info("blk4+1=0x%x\n", *(blk_4 + 1));
+ pr_info("blk4+2=0x%x\n", *(blk_4 + 2));
+
+}
+unsigned short rds_get_freq(void)
+{
+ return 0;
+}
+void rds_get_af_method(unsigned char AFH, unsigned char AFL)
+{
+ static signed short pre_af_num;
+ unsigned char indx, indx2, num;
+
+ pr_info("af code is %d and %d\n", AFH, AFL);
+ if (AFH >= RDS_AF_NUM_1 && AFH <= RDS_AF_NUM_25) {
+ if (AFH == RDS_AF_NUM_1) {
+ fmdev->rds_data.af_data.ismethod_a = RDS_AF_M_A;
+ fmdev->rds_data.af_data.AF_NUM = 1;
+ }
+ /* have got af number */
+ fmdev->rds_data.af_data.isafnum_get = 0;
+ pre_af_num = AFH - 224;
+ if (pre_af_num != fmdev->rds_data.af_data.AF_NUM)
+ fmdev->rds_data.af_data.AF_NUM = pre_af_num;
+ else
+ fmdev->rds_data.af_data.isafnum_get = 1;
+ if ((AFL < 205) && (AFL > 0)) {
+ fmdev->rds_data.af_data.AF[0][0] = AFL + 875;
+ /* convert to 100KHz */
+#ifdef SPRD_FM_50KHZ_SUPPORT
+ fmdev->rds_data.af_data.AF[0][0] *= 10;
+#endif
+ if ((fmdev->rds_data.af_data.AF[0][0]) !=
+ (fmdev->rds_data.af_data.AF[1][0])) {
+ fmdev->rds_data.af_data.AF[1][0] =
+ fmdev->rds_data.af_data.AF[0][0];
+ } else {
+ if (fmdev->rds_data.af_data.AF[1][0] !=
+ rds_get_freq())
+ fmdev->rds_data.af_data.ismethod_a = 1;
+ else
+ fmdev->rds_data.af_data.ismethod_a = 0;
+ }
+
+ /* only one AF handle */
+ if ((fmdev->rds_data.af_data.isafnum_get) &&
+ (fmdev->rds_data.af_data.AF_NUM == 1)) {
+ fmdev->rds_data.af_data.addr_cnt = 0xFF;
+ }
+ }
+ } else if ((fmdev->rds_data.af_data.isafnum_get) &&
+ (fmdev->rds_data.af_data.addr_cnt != 0xFF)) {
+ /* AF Num correct */
+ num = fmdev->rds_data.af_data.AF_NUM;
+ num = num >> 1;
+ /*
+ * Put AF freq fm_s32o buffer and check if AF
+ * freq is repeat again
+ */
+ for (indx = 1; indx < (num + 1); indx++) {
+ if ((AFH == (fmdev->rds_data.af_data.AF[0][2*num-1]))
+ && (AFL ==
+ (fmdev->rds_data.af_data.AF[0][2*indx]))) {
+ pr_info("AF same as\n");
+ break;
+ } else if (!(fmdev->rds_data.af_data.AF[0][2 * indx-1])
+ ) {
+ /* convert to 100KHz */
+ fmdev->rds_data.af_data.AF[0][2*indx-1] =
+ AFH + 875;
+ fmdev->rds_data.af_data.AF[0][2*indx] =
+ AFL + 875;
+#ifdef MTK_FM_50KHZ_SUPPORT
+ fmdev->rds_data.af_data.AF[0][2*indx-1] *= 10;
+ fmdev->rds_data.af_data.AF[0][2*indx] *= 10;
+#endif
+ break;
+ }
+ }
+ num = fmdev->rds_data.af_data.AF_NUM;
+ if (num <= 0)
+ return;
+ if ((fmdev->rds_data.af_data.AF[0][num-1]) == 0)
+ return;
+ num = num >> 1;
+ for (indx = 1; indx < num; indx++) {
+ for (indx2 = indx + 1; indx2 < (num + 1); indx2++) {
+ AFH = fmdev->rds_data.af_data.AF[0][2*indx-1];
+ AFL = fmdev->rds_data.af_data.AF[0][2*indx];
+ if (AFH > (fmdev->rds_data.af_data.AF[0][2*indx2
+ -1])) {
+ fmdev->rds_data.af_data.AF[0][2*indx-1]
+ = fmdev->rds_data.af_data.AF[0][2
+ *indx2-1];
+ fmdev->rds_data.af_data.AF[0][2*indx] =
+ fmdev->rds_data.af_data.AF[0][2*indx2];
+ fmdev->rds_data.af_data.AF[0][2*indx2-1]
+ = AFH;
+ fmdev->rds_data.af_data.AF[0][2*indx2]
+ = AFL;
+ } else if (AFH == (fmdev->rds_data.af_data
+ .AF[0][2*indx2-1])) {
+ if (AFL > (fmdev->rds_data.af_data.AF[0]
+ [2*indx2])) {
+ fmdev->rds_data.af_data.AF[0][2
+ *indx-1]
+ = fmdev->rds_data.af_data
+ .AF[0][2*indx2-1];
+ fmdev->rds_data.af_data.AF[0][2
+ *indx] = fmdev->rds_data
+ .af_data.AF[0][2*indx2];
+ fmdev->rds_data.af_data.AF[0][2*
+ indx2-1] = AFH;
+ fmdev->rds_data.af_data.AF[0][2
+ *indx2] = AFL;
+ }
+ }
+ }
+ }
+
+ /*
+ * arrange frequency from low to high:end
+ * compare AF buff0 and buff1 data:start
+ */
+ num = fmdev->rds_data.af_data.AF_NUM;
+ indx2 = 0;
+ for (indx = 0; indx < num; indx++) {
+ if ((fmdev->rds_data.af_data.AF[1][indx]) ==
+ (fmdev->rds_data.af_data.AF[0][indx])) {
+ if (fmdev->rds_data.af_data.AF[1][indx] != 0)
+ indx2++;
+ } else {
+ fmdev->rds_data.af_data.AF[1][indx] =
+ fmdev->rds_data.af_data.AF[0][indx];
+ }
+ }
+
+ /* compare AF buff0 and buff1 data:end */
+ if (indx2 == num) {
+ fmdev->rds_data.af_data.addr_cnt = 0xFF;
+ for (indx = 0; indx < num; indx++) {
+ if ((fmdev->rds_data.af_data.AF[1][indx])
+ == 0)
+ fmdev->rds_data.af_data.addr_cnt = 0x0F;
+ }
+ } else
+ fmdev->rds_data.af_data.addr_cnt = 0x0F;
+ }
+}
+/*
+ * Group types which contain this information: 0A
+ * AF = Alternative Frequencies
+ * af information in block 3
+ */
+
+void rds_get_af(unsigned char *buf)
+{
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+
+ if (*blk_3 != 1)
+ return;
+ rds_get_af_method(*(blk_3 + 1), *(blk_3 + 2));
+ fmdev->rds_data.af_data.AF[1][24] = 0;
+}
+
+/* Group types which contain this information: 0A 0B 15B */
+void rds_get_di_ms(unsigned char *buf)
+{
+}
+
+/*
+ * Group types which contain this information: TP_all(byte1 bit2);
+ * TA: 0A 0B 14B 15B(byte2 bit4)
+ * TP = Traffic Program identification; TA = Traffic Announcement
+ */
+
+void rds_get_tp_ta(unsigned char *buf, unsigned char grp_type)
+{
+ unsigned char *blk_2 = buf + rds_data_unit_size;
+ unsigned char byte1 = *(blk_2 + 1), byte2 = *(blk_2 + 2);
+ unsigned char ta_tp;
+ unsigned short *event = &(fmdev->rds_data.event_status);
+
+ if ((*blk_2) == 0)
+ return;
+ ta_tp = (unsigned char)((byte1 & (1<<2))>>2);
+ if (grp_type == 0x0a || grp_type == 0x0B || grp_type == 0xFB) {
+ ta_tp |= (byte2 & (1 << 4));
+ rds_event_set(event, RDS_EVENT_TAON_OFF);
+ }
+}
+
+/*
+ * Group types which contain this information: all
+ * block2:Programme Type code = 5 bits($)
+ * #### ##$$ $$$# ####
+ */
+
+void rds_get_pty(unsigned char *buf)
+{
+ unsigned char *blk_2 = buf + rds_data_unit_size;
+ unsigned char byte1 = *(blk_2 + 1), byte2 = *(blk_2 + 2);
+ unsigned char pty = 0;
+
+ if ((*blk_2) == 1)
+ pty = ((byte2 >> 5) | ((byte1 & 0x3) << 3));
+ fmdev->rds_data.PTY = pty;
+}
+
+/*
+ * Group types which contain this information: all
+ * Read PI code from the group. grp_typeA: block 1 and block3,
+ * grp_type B: block3
+ */
+
+void rds_get_pi_code(unsigned char *buf, unsigned char version)
+{
+ unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
+ /* pi_code for version A, pi_code_b for version B */
+ unsigned short pi_code = 0, pi_code_b = 0;
+ unsigned char crc_flag1 = *buf;
+ unsigned char crc_flag3 = *(buf + 2 * rds_data_unit_size);
+
+ if (version == invalid_grp_type)
+ return;
+
+ if (crc_flag1 == 1)
+ bytes_to_short(pi_code, buf+1);
+ else
+ return;
+
+ if (version == grp_ver_b) {
+ if (crc_flag3 == 1)
+ bytes_to_short(pi_code_b, blk_3 + 1);
+ }
+
+ if (pi_code == 0 && pi_code_b != 0)
+ pi_code = pi_code_b;
+/* send pi_code value to global and copy to user space in read rds interface */
+ fmdev->rds_data.PI = pi_code;
+}
+
+/*
+ * Block 1: PIcode(16bit)+CRC
+ * Block 2 : Group type code(4bit)
+ * B0 version(1bit 0:version A; 1:version B)
+ * TP(1bit)+ PTY(5 bits)
+ * @ buffer point to the start of Block 1
+ * Block3: 16bits + 10bits
+ * Block4: 16bits + 10bits
+ * rds_get_group_type from Block2
+ */
+unsigned char rds_get_group_type(unsigned char *buffer)
+{
+ unsigned char *crc_blk_2 = buffer + rds_data_unit_size;
+ unsigned char blk2_byte1 = *(crc_blk_2+1);
+ unsigned char group_type;
+ unsigned char crc_flag = *crc_blk_2;
+
+ if (crc_flag == 1)
+ group_type = (blk2_byte1 & grp_type_mask);
+ else
+ group_type = invalid_grp_type;
+ /* 0:version A, 1: version B */
+ if (blk2_byte1 & grp_ver_bit)
+ group_type |= grp_ver_b;
+ else
+ group_type |= grp_ver_a;
+
+ return group_type;
+}
+
+void dump_rx_data(unsigned char *buffer, unsigned int len)
+{
+ char i;
+
+ pr_info("\n fm rx data(%d): ", len);
+ for (i = 0; i < len; i++)
+ pr_info("0x%x__", *(buffer+i));
+ pr_info("\n");
+}
+
+/*
+ * rds_parser
+ * Block0: PI code(16bits)
+ * Block1: Group type(4bits), B0=version code(1bit),
+ * TP=traffic program code(1bit),
+ * PTY=program type code(5bits), other(5bits)
+ * @getfreq - function pointer, AF need get current freq
+ * Theoretically From FIFO :
+ * One Group = Block1(16 bits) + CRC(10 bits)
+ * Block2 +CRC(10 bits)
+ * Block3(16 bits) + CRC(10 bits)
+ * Block4(16 bits) + CRC(10 bits)
+ * From marlin2 chip, the data stream is like below:
+ * One Group = CRC_Flag(8bit)+Block1(16bits)
+ * CRC_Flag(8bit)+Block2(16bits)
+ * CRC_Flag(8bit)+Block3(16bits)
+ * CRC_Flag(8bit)+Block4(16bits)
+ */
+void rds_parser(unsigned char *buffer, unsigned char len, unsigned int fifo_id)
+{
+ unsigned char grp_type;
+
+ dump_rx_data(buffer, len);
+ grp_type = rds_get_group_type(buffer);
+ pr_info("group type is : 0x%x\n", grp_type);
+
+ rds_get_pi_code(buffer, grp_type & grp_ver_mask);
+ rds_get_pty(buffer);
+ rds_get_tp_ta(buffer, grp_type);
+
+ switch (grp_type) {
+ case invalid_grp_type:
+ pr_info("invalid group type\n");
+ break;
+ /* Processing group 0A */
+ case 0x0A:
+ rds_get_di_ms(buffer);
+ rds_get_af(buffer);
+ rds_get_ps(buffer);
+ break;
+ /* Processing group 0B */
+ case 0x0B:
+ rds_get_di_ms(buffer);
+ rds_get_ps(buffer);
+ break;
+ case 0x1A:
+ rds_get_slc(buffer);
+ rds_get_pin(buffer);
+ break;
+ case 0x1B:
+ rds_get_pin(buffer);
+ break;
+ case 0x2A:
+ case 0x2B:
+ rds_get_rt(buffer, grp_type);
+ break;
+ case 0x3A:
+ rds_get_oda_aid(buffer);
+ break;
+ case 0x4A:
+ rds_get_ct(buffer);
+ break;
+ case 0x5A:
+ case 0x5B:
+ rds_get_tdc(buffer, grp_type & grp_ver_mask);
+ break;
+ case 0x9a:
+ rds_get_ews(buffer);
+ break;
+ /* 10A group */
+ case 0xAA:
+ rds_get_ptyn(buffer);
+ break;
+ case 0xEA:
+ rds_get_eon(buffer);
+ break;
+ case 0xEB:
+ rds_get_eon_ta(buffer);
+ break;
+ case 0xFB:
+ rds_get_di_ms(buffer);
+ break;
+/* ODA (Open Data Applications) group availability signaled in type 3A groups */
+ case 0x3B:
+ case 0x4B:
+ case 0x6A:
+ case 0x6B:
+ case 0x7A:
+ case 0x7B:
+ case 0x8A:
+ case 0x8B:
+ case 0x9B:
+ case 0xAB:
+ case 0xBA:
+ case 0xBB:
+ case 0xCA:
+ case 0xCB:
+ case 0xDB:
+ case 0xDA:
+ case 0xFA:
+ rds_get_oda(buffer);
+ break;
+ default:
+ pr_info("rds group type[0x%x] not to be processed\n", grp_type);
+ break;
+ }
+ sdiom_pt_read_release(fifo_id);
+ pr_info("fmdrv release fifo_id is %d\n", fifo_id);
+}
+
diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h
new file mode 100644
index 0000000..404dc28
--- /dev/null
+++ b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h
@@ -0,0 +1,103 @@
+/*
+ * SPREADTRUM SC2342 FM Radio driver
+ *
+ * Copyright (C) 2015~2017 Spreadtrum, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#ifndef _FMDRV_RDS_PARSER
+#define _FMDRV_RDS_PARSER
+
+/* Block1 */
+#define RDS_BLCKA 0x00
+/* Block2 */
+#define RDS_BLCKB 0x10
+/* Block3 */
+#define RDS_BLCKC 0x20
+/* Block4 */
+#define RDS_BLCKD 0x30
+/* BlockC hyphen */
+#define RDS_BLCKC_C 0x40
+/* BlockE in RBDS */
+#define RDS_BLCKE_B 0x50
+/* Block E */
+#define RDS_BLCKE 0x60
+
+/* 3bytes = 8bit(CRC flag) + 16bits (1 block ) */
+#define rds_data_unit_size 3
+#define rds_data_group_size (3*4)
+#define grp_type_mask 0xF0
+#define grp_ver_mask 0x0F
+/* 0:version A, 1: version B */
+#define grp_ver_bit (0x01<<3)
+#define grp_ver_a 0x0A
+#define grp_ver_b 0x0B
+#define invalid_grp_type 0x00
+
+/* AF fill in code */
+#define RDS_AF_FILL 205
+/* AF invalid code low marker */
+#define RDS_AF_INVAL_L 205
+/* AF invalid code middle marker */
+#define RDS_AF_INVAL_M 223
+/* 0 AF follow */
+#define RDS_AF_NONE 224
+/* 1 AF follow */
+#define RDS_AF_NUM_1 225
+/* 25 AFs follow */
+#define RDS_AF_NUM_25 249
+/* LF/MF follow */
+#define RDS_LF_MF 250
+/* AF invalid code high marker */
+#define RDS_AF_INVAL_H 251
+/* AF invalid code top marker */
+#define RDS_AF_INVAL_T 255
+/* lowest MF frequency */
+#define RDS_MF_LOW 0x10
+
+/* FM base frequency */
+#define RDS_FM_BASE 875
+/* MF base frequency */
+#define RDS_MF_BASE 531
+/* LF base frequency */
+#define RDS_LF_BASE 153
+
+/* minimum day */
+#define RDS_MIN_DAY 1
+/* maximum day */
+#define RDS_MAX_DAY 31
+/* minimum hour */
+#define RDS_MIN_HUR 0
+/* maximum hour */
+#define RDS_MAX_HUR 23
+/* minimum minute */
+#define RDS_MIN_MUT 0
+/* maximum minute */
+#define RDS_MAX_MUT 59
+/* left over rds data length max in control block */
+#define BTA_RDS_LEFT_LEN 24
+/* Max radio text length */
+#define BTA_RDS_RT_LEN 64
+/* 8 character RDS feature length, i.e. PS, PTYN */
+#define BTA_RDS_LEN_8 8
+
+/* AF encoding method */
+enum {
+ /* unknown */
+ RDS_AF_M_U,
+ /* method - A */
+ RDS_AF_M_A,
+ /* method - B */
+ RDS_AF_M_B
+};
+
+/* change 8 bits to 16bits */
+#define bytes_to_short(dest, src) (dest = (unsigned short)(((unsigned short)\
+ (*(src)) << 8) + (unsigned short)(*((src) + 1))))
+
+void dump_rx_data(unsigned char *buffer, unsigned int len);
+void rds_parser(unsigned char *buffer, unsigned char len,
+ unsigned int fifo_id);
+
+#endif /* _FMDRV_RDS_PARSER */
--
2.7.4