linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] add support for Spreadtrum's FM driver
@ 2017-07-04 10:15 Chunyan Zhang
  2017-07-04 10:15 ` [PATCH 1/2] arm64: dts: add Spreadtrum's fm support Chunyan Zhang
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Chunyan Zhang @ 2017-07-04 10:15 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Arnd Bergmann
  Cc: linux-kernel, devicetree, linux-arm-kernel, Songhe Wei,
	Zhongping Tan, Orson Zhai, Chunyan Zhang, Chunyan Zhang

According to GregKH's suggestion [1], we tried to simply sort out the
FM driver source code which has been using in the internal projects.

Hopes it can help for fixing the problem raised in [1].

[1] https://lkml.org/lkml/2017/6/28/222

Chunyan Zhang (2):
  arm64: dts: add Spreadtrum's fm support
  misc: added Spreadtrum's radio driver

 arch/arm64/boot/dts/sprd/sp9860g-1h10.dts      |    4 +
 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 ++
 14 files changed, 3308 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

-- 
2.7.4

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/2] arm64: dts: add Spreadtrum's fm support
  2017-07-04 10:15 [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang
@ 2017-07-04 10:15 ` Chunyan Zhang
  2017-07-04 12:35   ` Mark Rutland
  2017-07-04 10:15 ` [PATCH 2/2] misc: added Spreadtrum's radio driver Chunyan Zhang
  2017-07-05 10:25 ` [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang
  2 siblings, 1 reply; 8+ messages in thread
From: Chunyan Zhang @ 2017-07-04 10:15 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Arnd Bergmann
  Cc: linux-kernel, devicetree, linux-arm-kernel, Songhe Wei,
	Zhongping Tan, Orson Zhai, Chunyan Zhang, Chunyan Zhang

Added FM support for Spreadtrum's SP9860 board.

Signed-off-by: Songhe Wei <songhe.wei@spreadtrum.com>
Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com>
---
 arch/arm64/boot/dts/sprd/sp9860g-1h10.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts b/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts
index 0362ecd..6fe052d 100644
--- a/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts
+++ b/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts
@@ -39,6 +39,10 @@
 		#size-cells = <2>;
 		ranges;
 	};
+
+	sprd-fm {
+		compatible  = "sprd,marlin2-fm";
+	};
 };
 
 &uart0 {
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] misc: added Spreadtrum's radio driver
  2017-07-04 10:15 [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang
  2017-07-04 10:15 ` [PATCH 1/2] arm64: dts: add Spreadtrum's fm support Chunyan Zhang
@ 2017-07-04 10:15 ` Chunyan Zhang
  2017-07-04 10:51   ` Arnd Bergmann
  2017-07-05 10:25 ` [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang
  2 siblings, 1 reply; 8+ messages in thread
From: Chunyan Zhang @ 2017-07-04 10:15 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Arnd Bergmann
  Cc: linux-kernel, devicetree, linux-arm-kernel, Songhe Wei,
	Zhongping Tan, Orson Zhai, Chunyan Zhang, Chunyan Zhang

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@spreadtrum.com>
Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com>
---
 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@spreadtrum.com>");
+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

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] misc: added Spreadtrum's radio driver
  2017-07-04 10:15 ` [PATCH 2/2] misc: added Spreadtrum's radio driver Chunyan Zhang
@ 2017-07-04 10:51   ` Arnd Bergmann
  2017-07-05 10:18     ` Chunyan Zhang
  0 siblings, 1 reply; 8+ messages in thread
From: Arnd Bergmann @ 2017-07-04 10:51 UTC (permalink / raw)
  To: Chunyan Zhang
  Cc: Greg Kroah-Hartman, Linux Kernel Mailing List, devicetree,
	Linux ARM, Songhe Wei, Zhongping Tan, Orson Zhai, Chunyan Zhang

On Tue, Jul 4, 2017 at 12:15 PM, Chunyan Zhang
<chunyan.zhang@spreadtrum.com> wrote:
> 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@spreadtrum.com>
> Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com>

(adding linux-media folks to Cc)

Hi Chunyan,

Thanks for posting this for inclusion as Greg asked for. I'm not sure what
the policy is for new radio drivers, but I assume this would have to go
to drivers/staging/media/ as it is a driver for hardware that fits into
drivers/media/radio but doesn't use the respective APIs.

        Arnd
---
end of message, full patch quoted for reference below

> ---
>  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@spreadtrum.com>");
> +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
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] arm64: dts: add Spreadtrum's fm support
  2017-07-04 10:15 ` [PATCH 1/2] arm64: dts: add Spreadtrum's fm support Chunyan Zhang
@ 2017-07-04 12:35   ` Mark Rutland
  0 siblings, 0 replies; 8+ messages in thread
From: Mark Rutland @ 2017-07-04 12:35 UTC (permalink / raw)
  To: Chunyan Zhang
  Cc: Greg Kroah-Hartman, Arnd Bergmann, linux-kernel, devicetree,
	linux-arm-kernel, Songhe Wei, Zhongping Tan, Orson Zhai,
	Chunyan Zhang

On Tue, Jul 04, 2017 at 06:15:07PM +0800, Chunyan Zhang wrote:
> Added FM support for Spreadtrum's SP9860 board.
> 
> Signed-off-by: Songhe Wei <songhe.wei@spreadtrum.com>
> Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com>
> ---
>  arch/arm64/boot/dts/sprd/sp9860g-1h10.dts | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts b/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts
> index 0362ecd..6fe052d 100644
> --- a/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts
> +++ b/arch/arm64/boot/dts/sprd/sp9860g-1h10.dts
> @@ -39,6 +39,10 @@
>  		#size-cells = <2>;
>  		ranges;
>  	};
> +
> +	sprd-fm {
> +		compatible  = "sprd,marlin2-fm";
> +	};

What kind of device is this? MMIO?

It seems to be sitting directly under the root node, but has no reg or
relationship with another bus defined, so something is missing.

This will need binding documentation.

Thanks,
Mark.


>  };
>  
>  &uart0 {
> -- 
> 2.7.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] misc: added Spreadtrum's radio driver
  2017-07-04 10:51   ` Arnd Bergmann
@ 2017-07-05 10:18     ` Chunyan Zhang
  2017-07-05 11:43       ` Arnd Bergmann
  0 siblings, 1 reply; 8+ messages in thread
From: Chunyan Zhang @ 2017-07-05 10:18 UTC (permalink / raw)
  To: Arnd Bergmann, Mauro Carvalho Chehab
  Cc: Chunyan Zhang, Greg Kroah-Hartman, Linux Kernel Mailing List,
	devicetree, Linux ARM, Songhe Wei, Zhongping Tan, Orson Zhai,
	linux-media

On 4 July 2017 at 18:51, Arnd Bergmann <arnd@arndb.de> wrote:
> On Tue, Jul 4, 2017 at 12:15 PM, Chunyan Zhang
> <chunyan.zhang@spreadtrum.com> wrote:
>> 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@spreadtrum.com>
>> Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com>
>
> (adding linux-media folks to Cc)

(You forgot to add them in :))

>
> Hi Chunyan,

Hi Arnd,

>
> Thanks for posting this for inclusion as Greg asked for. I'm not sure what
> the policy is for new radio drivers, but I assume this would have to go
> to drivers/staging/media/ as it is a driver for hardware that fits into
> drivers/media/radio but doesn't use the respective APIs.

Ok, I agree to let it go to drivers/staging/media/.

Like I mentioned, SC2342 includes many functions, this patch is only
adding FM radio function included in SC2342 to the kernel tree.  So I
figure that its lifetime probably will not be too long, will remove it
from the kernel tree when we have a clean enough version of the whole
SC2342 drivers for the official upstreaming.

Thanks,
Chunyan

>
>         Arnd
> ---
> end of message, full patch quoted for reference below
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 0/2] add support for Spreadtrum's FM driver
  2017-07-04 10:15 [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang
  2017-07-04 10:15 ` [PATCH 1/2] arm64: dts: add Spreadtrum's fm support Chunyan Zhang
  2017-07-04 10:15 ` [PATCH 2/2] misc: added Spreadtrum's radio driver Chunyan Zhang
@ 2017-07-05 10:25 ` Chunyan Zhang
  2 siblings, 0 replies; 8+ messages in thread
From: Chunyan Zhang @ 2017-07-05 10:25 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Arnd Bergmann, Mauro Carvalho Chehab
  Cc: linux-kernel, devicetree, linux-arm-kernel, linux-media,
	Songhe Wei, Zhongping Tan, Orson Zhai, Chunyan Zhang,
	Chunyan Zhang

[add linux-media list and Mauro Carvalho Chehab]

According to GregKH's suggestion [1], we tried to simply sort out the
FM driver source code which has been using in the internal projects.

Hopes it can help for fixing the problem raised in [1].

[1] https://lkml.org/lkml/2017/6/28/222

Chunyan Zhang (2):
  arm64: dts: add Spreadtrum's fm support
  misc: added Spreadtrum's radio driver

 arch/arm64/boot/dts/sprd/sp9860g-1h10.dts      |    4 +
 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 ++
 14 files changed, 3308 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

-- 
2.7.4

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] misc: added Spreadtrum's radio driver
  2017-07-05 10:18     ` Chunyan Zhang
@ 2017-07-05 11:43       ` Arnd Bergmann
  0 siblings, 0 replies; 8+ messages in thread
From: Arnd Bergmann @ 2017-07-05 11:43 UTC (permalink / raw)
  To: Chunyan Zhang
  Cc: Mauro Carvalho Chehab, Chunyan Zhang, Greg Kroah-Hartman,
	Linux Kernel Mailing List, devicetree, Linux ARM, Songhe Wei,
	Zhongping Tan, Orson Zhai, Linux Media Mailing List

On Wed, Jul 5, 2017 at 12:18 PM, Chunyan Zhang <zhang.lyra@gmail.com> wrote:
> On 4 July 2017 at 18:51, Arnd Bergmann <arnd@arndb.de> wrote:
>> On Tue, Jul 4, 2017 at 12:15 PM, Chunyan Zhang
> Like I mentioned, SC2342 includes many functions, this patch is only
> adding FM radio function included in SC2342 to the kernel tree.  So I
> figure that its lifetime probably will not be too long, will remove it
> from the kernel tree when we have a clean enough version of the whole
> SC2342 drivers for the official upstreaming.

Would it make sense to add some or all of the other drivers to drivers/staging/
as well in the meantime?

       Arnd

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2017-07-05 11:43 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-07-04 10:15 [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang
2017-07-04 10:15 ` [PATCH 1/2] arm64: dts: add Spreadtrum's fm support Chunyan Zhang
2017-07-04 12:35   ` Mark Rutland
2017-07-04 10:15 ` [PATCH 2/2] misc: added Spreadtrum's radio driver Chunyan Zhang
2017-07-04 10:51   ` Arnd Bergmann
2017-07-05 10:18     ` Chunyan Zhang
2017-07-05 11:43       ` Arnd Bergmann
2017-07-05 10:25 ` [PATCH 0/2] add support for Spreadtrum's FM driver Chunyan Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).