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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




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

(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@xxxxxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION(FM_VERSION);
> diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_main.h b/drivers/misc/sprd-wcn/radio/fmdrv_main.h
> new file mode 100644
> index 0000000..7dc3e39
> --- /dev/null
> +++ b/drivers/misc/sprd-wcn/radio/fmdrv_main.h
> @@ -0,0 +1,117 @@
> +/*
> + * SPREADTRUM SC2342 FM Radio driver
> + *
> + * Copyright (C) 2015~2017 Spreadtrum, Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef _FMDRV_MAIN_H
> +#define _FMDRV_MAIN_H
> +
> +#include <linux/fs.h>
> +
> +#define FM_OFF                 0x00
> +#define FM_POWERUP_CMD         0x00
> +#define FM_TUNE_CMD            0x01
> +#define FM_MUTE_CMD 0x02
> +#define FM_SCAN_ALL_CMD 0x03
> +#define FM_SEEK_CMD 0x04
> +#define FM_SEARCH_ABORT 0X05
> +#define FM_SET_RDS_MODE 0x06
> +#define FM_SET_RDS_TYPE 0x07
> +/* audio mode:0:mono, 1:stereo; 2:blending */
> +#define FM_SET_AUDIO_MODE 0x08
> +#define FM_SET_AF_ONOFF 0x09
> +/* #define FM_SET_AUDIO_PATH 0x09 */
> +#define FM_SET_REGION 0x0A
> +#define FM_SET_SCAN_STEP 0x0B
> +#define FM_CONFIG_DEEMPHASIS 0x0C
> +#define FM_GET_CURPAMD 0x0D
> +/* audio mode:0:mono, 1:stereo; 2:blending */
> +#define FM_GET_AUDIO_MODE 0x0E
> +#define FM_GET_VOLUME_CMD              0x0F
> +#define FM_SET_VOLUME_CMD              0x10
> +#define DM_GET_CUR_BLER_CMD    0x11
> +#define FM_POWERDOWN_CMD 0x12
> +#define FM_GET_RSSI_CMD 0x13
> +#define FM_GET_SNR_CMD 0x14
> +#define FM_SOFTMUTE_ONOFF_CMD 0x15
> +#define FM_SET_DEEMPHASIS_CMD          0x16
> +/* Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI */
> +#define FM_SET_SEEK_CRITERIA_CMD       0x17
> +/* softmute ,blending ,snr_th */
> +#define FM_SET_AUDIO_THRESHOLD_CMD 0x18
> +/* Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI */
> +#define FM_GET_SEEK_CRITERIA_CMD       0x19
> +/* softmute ,blending ,snr_th */
> +#define FM_GET_AUDIO_THRESHOLD_CMD 0x1A
> +#define FM_SET_ANA_SWITCH_CMD          0x1B
> +
> +#define FM_READ_WRITE_REG_CMD          0x22
> +
> +extern struct fmdrv_ops *fmdev;
> +
> +int fm_open(struct inode *inode, struct file *filep);
> +int fm_powerup(void *arg);
> +int fm_powerdown(void);
> +int fm_tune(void *arg);
> +int fm_seek(void *arg);
> +int fm_mute(void *arg);
> +int fm_getrssi(void *arg);
> +int fm_getcur_pamd(void *arg);
> +int fm_rds_onoff(void *arg);
> +int fm_ana_switch(void *arg);
> +int fm_af_onoff(void *arg);
> +int fm_set_volume(void *arg);
> +int fm_get_volume(void *arg);
> +int fm_stop_scan(void *arg);
> +int fm_scan_all(void *arg);
> +int fm_rw_reg(void *arg);
> +int fm_get_monostero(void *arg);
> +int fm_scan_all(void *arg);
> +int fm_rw_reg(void *arg);
> +int fm_stop_scan(void *arg);
> +int fm_rw_reg(void *arg);
> +int fm_get_monostero(void *arg);
> +int fm_set_audio_mode(void *arg);
> +int fm_set_region(void *arg);
> +int fm_set_scan_step(void *arg);
> +int fm_config_deemphasis(void *arg);
> +int fm_get_audio_mode(void *arg);
> +int fm_get_current_bler(void *arg);
> +int fm_get_cur_snr(void *arg);
> +int fm_softmute_onoff(void *arg);
> +int fm_set_seek_criteria(void *arg);
> +int fm_set_audio_threshold(void *arg);
> +int fm_get_seek_criteria(void *arg);
> +int fm_get_audio_threshold(void *arg);
> +ssize_t fm_read_rds_data(struct file *filp, char __user *buf,
> +       size_t count, loff_t *pos);
> +int fm_sdio_write(unsigned char *buffer, unsigned int size);
> +struct fm_rds_data *get_rds_data(void);
> +int start_marlin(int type);
> +int stop_marlin(int type);
> +unsigned int sdiom_pt_read_release(unsigned int fifo_id);
> +
> +struct fm_cmd_hdr {
> +       /* 01:cmd; 04:event */
> +       unsigned char header;
> +       /* vendor specific command 0xFC8C */
> +       unsigned short opcode;
> +       /* Number of bytes follows */
> +       unsigned char len;
> +       /* FM Sub Command */
> +       unsigned char fm_subcmd;
> +} __packed;
> +
> +struct fm_event_hdr {
> +       /* 01:cmd; 04:event */
> +       unsigned char header;
> +       /* 0e:cmd complete event; FF:vendor specific event */
> +       unsigned char id;
> +       /* Number of bytes follows */
> +       unsigned char len;
> +} __packed;
> +
> +#endif /* _FMDRV_MAIN_H */
> diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_ops.c b/drivers/misc/sprd-wcn/radio/fmdrv_ops.c
> new file mode 100644
> index 0000000..bd3ec3f
> --- /dev/null
> +++ b/drivers/misc/sprd-wcn/radio/fmdrv_ops.c
> @@ -0,0 +1,447 @@
> +/*
> + * SPREADTRUM SC2342 FM Radio driver
> + *
> + * Copyright (C) 2015~2017 Spreadtrum, Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <linux/compat.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/fs.h>
> +#include <linux/ioctl.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/sysfs.h>
> +#include <linux/sched.h>
> +#include <linux/uaccess.h>
> +#include <linux/wait.h>
> +
> +#ifdef CONFIG_OF
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#endif
> +
> +#include "fmdrv.h"
> +#include "fmdrv_main.h"
> +#include "fmdrv_ops.h"
> +
> +static long fm_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
> +{
> +       void __user *argp = (void __user *)arg;
> +       long ret = 0;
> +       u32 iarg = 0;
> +
> +       pr_debug("FM_IOCTL cmd: 0x%x.\n", cmd);
> +       switch (cmd) {
> +       case FM_IOCTL_POWERUP:
> +               fm_powerup(argp);
> +               ret = fm_tune(argp);
> +               break;
> +
> +       case FM_IOCTL_POWERDOWN:
> +               ret = fm_powerdown();
> +               break;
> +
> +       case FM_IOCTL_TUNE:
> +               ret = fm_tune(argp);
> +               break;
> +
> +       case FM_IOCTL_SEEK:
> +               ret = fm_seek(argp);
> +               break;
> +
> +       case FM_IOCTL_SETVOL:
> +               pr_info("fm ioctl set volume\n");
> +               ret = fm_set_volume(argp);
> +               break;
> +
> +       case FM_IOCTL_GETVOL:
> +               pr_info("fm ioctl get volume\n");
> +               ret = fm_get_volume(argp);
> +               break;
> +
> +       case FM_IOCTL_MUTE:
> +               ret = fm_mute(argp);
> +               break;
> +
> +       case FM_IOCTL_GETRSSI:
> +               pr_info("fm ioctl get RSSI\n");
> +               ret = fm_getrssi(argp);
> +               break;
> +
> +       case FM_IOCTL_SCAN:
> +               pr_info("fm ioctl SCAN\n");
> +               ret = fm_scan_all(argp);
> +               break;
> +
> +       case FM_IOCTL_STOP_SCAN:
> +               pr_info("fm ioctl STOP SCAN\n");
> +               ret = fm_stop_scan(argp);
> +               break;
> +
> +       case FM_IOCTL_GETCHIPID:
> +               pr_info("fm ioctl GET chipID\n");
> +               iarg = 0x2341;
> +               if (copy_to_user(argp, &iarg, sizeof(iarg)))
> +                       ret = -EFAULT;
> +               else
> +                       ret = 0;
> +               break;
> +
> +       case FM_IOCTL_EM_TEST:
> +               pr_info("fm ioctl EM_TEST\n");
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_RW_REG:
> +               pr_info("fm ioctl RW_REG\n");
> +               ret = fm_rw_reg(argp);
> +               break;
> +
> +       case FM_IOCTL_GETMONOSTERO:
> +               pr_info("fm ioctl GETMONOSTERO\n");
> +               ret = fm_get_monostero(argp);
> +               break;
> +       case FM_IOCTL_GETCURPAMD:
> +               pr_info("fm ioctl get PAMD\n");
> +               ret = fm_getcur_pamd(argp);
> +               break;
> +
> +       case FM_IOCTL_GETGOODBCNT:
> +       case FM_IOCTL_GETBADBNT:
> +       case FM_IOCTL_GETBLERRATIO:
> +       case FM_IOCTL_RDS_SIM_DATA:
> +       case FM_IOCTL_IS_FM_POWERED_UP:
> +       case FM_IOCTL_OVER_BT_ENABLE:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_RDS_ONOFF:
> +               pr_info("----RDS_ONOFF----");
> +               ret = fm_rds_onoff(argp);
> +               break;
> +
> +       case FM_IOCTL_RDS_SUPPORT:
> +               pr_info("fm ioctl is RDS_SUPPORT\n");
> +               ret = 0;
> +               if (copy_from_user(&iarg, (void __user *)arg, sizeof(iarg))) {
> +                       pr_err("fm RDS support 's ret value is -eFAULT\n");
> +                       return -EFAULT;
> +               }
> +               iarg = FM_RDS_ENABLE;
> +               if (copy_to_user((void __user *)arg, &iarg, sizeof(iarg)))
> +                       ret = -EFAULT;
> +               break;
> +
> +       case FM_IOCTL_ANA_SWITCH:
> +               ret = fm_ana_switch(argp);
> +               break;
> +
> +       case FM_IOCTL_GETCAPARRAY:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_I2S_SETTING:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_RDS_GROUPCNT:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_RDS_GET_LOG:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_SCAN_GETRSSI:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_SETMONOSTERO:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_RDS_BC_RST:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_CQI_GET:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_GET_HW_INFO:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_GET_I2S_INFO:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_IS_DESE_CHAN:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_TOP_RDWR:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_HOST_RDWR:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_PRE_SEARCH:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_RESTORE_SEARCH:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_GET_AUDIO_INFO:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_SCAN_NEW:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_SEEK_NEW:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_TUNE_NEW:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_SOFT_MUTE_TUNE:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_DESENSE_CHECK:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_FULL_CQI_LOG:
> +               ret = 0;
> +               break;
> +
> +       case FM_IOCTL_SET_AUDIO_MODE:
> +               ret = fm_set_audio_mode(argp);
> +               break;
> +
> +       case FM_IOCTL_SET_REGION:
> +               ret = fm_set_region(argp);
> +               break;
> +
> +       case FM_IOCTL_SET_SCAN_STEP:
> +               ret = fm_set_scan_step(argp);
> +               break;
> +
> +       case FM_IOCTL_CONFIG_DEEMPHASIS:
> +               ret = fm_config_deemphasis(argp);
> +               break;
> +
> +       case FM_IOCTL_GET_AUDIO_MODE:
> +               ret = fm_get_audio_mode(argp);
> +               break;
> +
> +       case FM_IOCTL_GET_CUR_BLER:
> +               ret = fm_get_current_bler(argp);
> +               break;
> +
> +       case FM_IOCTL_GET_SNR:
> +               ret = fm_get_cur_snr(argp);
> +               break;
> +
> +       case FM_IOCTL_SOFTMUTE_ONOFF:
> +               ret = fm_softmute_onoff(argp);
> +               break;
> +
> +       case FM_IOCTL_SET_SEEK_CRITERIA:
> +               ret = fm_set_seek_criteria(argp);
> +               break;
> +
> +       case FM_IOCTL_SET_AUDIO_THRESHOLD:
> +               ret = fm_set_audio_threshold(argp);
> +               break;
> +
> +       case FM_IOCTL_GET_SEEK_CRITERIA:
> +               ret = fm_get_seek_criteria(argp);
> +               break;
> +
> +       case FM_IOCTL_GET_AUDIO_THRESHOLD:
> +               ret = fm_get_audio_threshold(argp);
> +               break;
> +
> +       case FM_IOCTL_AF_ONOFF:
> +               ret = fm_af_onoff(argp);
> +               break;
> +
> +       case FM_IOCTL_DUMP_REG:
> +               ret = 0;
> +               break;
> +
> +       default:
> +               pr_info("Unknown FM IOCTL cmd=0x%x.\n", cmd);
> +               return -EINVAL;
> +       }
> +
> +       return ret;
> +}
> +
> +static int fm_release(struct inode *inode, struct file *filep)
> +{
> +       pr_info("fm_misc_release.\n");
> +       fm_powerdown();
> +       stop_marlin(MARLIN_FM);
> +       wake_up_interruptible(&fmdev->rds_han.rx_queue);
> +       fmdev->rds_han.new_data_flag = 1;
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_COMPAT
> +static long fm_compat_ioctl(struct file *file,
> +                       unsigned int cmd, unsigned long data)
> +{
> +       pr_info("start_fm_compat_ioctl FM_IOCTL cmd: 0x%x.\n", cmd);
> +       cmd = cmd & 0xFFF0FFFF;
> +       cmd = cmd | 0x00080000;
> +       pr_info("fm_compat_ioctl FM_IOCTL cmd: 0x%x.\n", cmd);
> +       return fm_ioctl(file, cmd, (unsigned long)compat_ptr(data));
> +}
> +#endif
> +
> +const struct file_operations fm_misc_fops = {
> +       .owner = THIS_MODULE,
> +       .open = fm_open,
> +       .read = fm_read_rds_data,
> +       .unlocked_ioctl = fm_ioctl,
> +#ifdef CONFIG_COMPAT
> +       .compat_ioctl = fm_compat_ioctl,
> +#endif
> +       .release = fm_release,
> +};
> +
> +struct miscdevice fm_misc_device = {
> +       .minor = MISC_DYNAMIC_MINOR,
> +       .name = FM_DEV_NAME,
> +       .fops = &fm_misc_fops,
> +};
> +
> +#ifdef CONFIG_OF
> +
> +static const struct of_device_id  of_match_table_fm[] = {
> +       { .compatible = "sprd,marlin2-fm", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, of_match_table_fm);
> +#endif
> +
> +static int fm_probe(struct platform_device *pdev)
> +{
> +       int ret = -EINVAL;
> +       char *ver_str = FM_VERSION;
> +
> +#ifdef CONFIG_OF
> +       struct device_node *np;
> +
> +       np = pdev->dev.of_node;
> +#endif
> +
> +       pr_info(" marlin2 FM driver\n");
> +       pr_info(" Version: %s\n", ver_str);
> +
> +       ret = misc_register(&fm_misc_device);
> +       if (ret < 0) {
> +
> +               pr_info("misc_register failed!\n");
> +               return ret;
> +       }
> +
> +       pr_info("fm_init success.\n");
> +
> +       return 0;
> +}
> +
> +static int fm_remove(struct platform_device *pdev)
> +{
> +
> +       pr_info("exit_fm_driver!\n");
> +       misc_deregister(&fm_misc_device);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int fm_suspend(struct device *dev)
> +{
> +       return 0;
> +}
> +
> +static int fm_resume(struct device *dev)
> +{
> +       return 0;
> +}
> +#endif
> +
> +static const struct dev_pm_ops fm_pmops = {
> +       SET_SYSTEM_SLEEP_PM_OPS(fm_suspend, fm_resume)
> +};
> +
> +static struct platform_driver fm_driver = {
> +       .driver = {
> +               .name = "sprd-fm",
> +               .owner = THIS_MODULE,
> +#ifdef CONFIG_OF
> +                .of_match_table = of_match_ptr(of_match_table_fm),
> +#endif
> +               .pm = &fm_pmops,
> +       },
> +       .probe = fm_probe,
> +       .remove = fm_remove,
> +};
> +
> +#ifndef CONFIG_OF
> +struct platform_device fm_device = {
> +       .name = "sprd-fm",
> +       .id = -1,
> +};
> +#endif
> +
> +int  fm_device_init_driver(void)
> +{
> +       int ret;
> +#ifndef CONFIG_OF
> +       ret = platform_device_register(&fm_device);
> +       if (ret) {
> +               pr_info("fm: platform_device_register failed: %d\n", ret);
> +               return ret;
> +       }
> +#endif
> +       ret = platform_driver_register(&fm_driver);
> +       if (ret) {
> +#ifndef CONFIG_OF
> +               platform_device_unregister(&fm_device);
> +#endif
> +               pr_info("fm: probe failed: %d\n", ret);
> +       }
> +       pr_info("fm: probe success: %d\n", ret);
> +
> +       return ret;
> +}
> +
> +void fm_device_exit_driver(void)
> +{
> +       platform_driver_unregister(&fm_driver);
> +
> +}
> diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_ops.h b/drivers/misc/sprd-wcn/radio/fmdrv_ops.h
> new file mode 100644
> index 0000000..b3a019e
> --- /dev/null
> +++ b/drivers/misc/sprd-wcn/radio/fmdrv_ops.h
> @@ -0,0 +1,17 @@
> +/*
> + * SPREADTRUM SC2342 FM Radio driver
> + *
> + * Copyright (C) 2015~2017 Spreadtrum, Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +
> +#ifndef _FMDRV_OPS_H
> +#define _FMDRV_OPS_H
> +
> +extern struct fmdrv_ops *fmdev;
> +int  fm_device_init_driver(void);
> +void fm_device_exit_driver(void);
> +
> +#endif /* _FMDRV_OPS_H */
> diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
> new file mode 100644
> index 0000000..538b3b9
> --- /dev/null
> +++ b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
> @@ -0,0 +1,753 @@
> +/*
> + * SPREADTRUM SC2342 FM Radio driver
> + *
> + * Copyright (C) 2015~2017 Spreadtrum, Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +#include "fmdrv.h"
> +#include "fmdrv_main.h"
> +#include "fmdrv_rds_parser.h"
> +
> +static struct fm_rds_data *g_rds_data_p;
> +/* the next ps: index = 0 */
> +static unsigned char flag_next = 1;
> +void rds_parser_init(void)
> +{
> +       g_rds_data_p = get_rds_data();
> +}
> +
> +void  fmr_assert(unsigned short *a)
> +{
> +       if (a == NULL)
> +               pr_info("%s,invalid pointer\n", __func__);
> +}
> +
> +/*
> + * rds_event_set
> + * To set rds event, and user space can use this flag to juge
> + * which event happened
> + * If success return 0, else return error code
> + */
> +static signed int rds_event_set(unsigned short *events, signed int event_mask)
> +{
> +       fmr_assert(events);
> +       *events |= event_mask;
> +       wake_up_interruptible(&fmdev->rds_han.rx_queue);
> +       fmdev->rds_han.new_data_flag = 1;
> +
> +       return 0;
> +}
> +
> +/*
> + * Group types which contain this information:
> + * TA(Traffic Program) code 0A 0B 14B 15B
> + */
> +void rds_get_eon_ta(unsigned char *buf)
> +{
> +       unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
> +       unsigned char data = *(buf + rds_data_unit_size + 2);
> +       unsigned char ta_tp;
> +       unsigned int pi_on;
> +
> +       if (*blk_4  == 0)
> +               return;
> +       /* bit3: TA ON  bit4: TP ON */
> +       ta_tp = (unsigned char)(((data & (1 << 4)) >> 4) | ((data & (1 << 3))
> +                       << 1));
> +       bytes_to_short(pi_on, blk_4 + 1);
> +       /* need add some code to adapter google upper layer  here */
> +}
> +
> +/*
> + * EON = Enhanced Other Networks information
> + * Group types which contain this information: EON : 14A
> + * variant code is in blockB low 4 bits
> + */
> +void rds_get_eon(unsigned char *buf)
> +{
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +       unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
> +       unsigned short pi_on;
> +
> +       if ((*blk_3 == 0) || (*blk_4 == 0))
> +               return;
> +       /* if the upper Layer true */
> +       bytes_to_short(pi_on, blk_4 + 1);
> +}
> +
> +/*
> + * PTYN = Programme TYpe Name
> + * From Group 10A, it's a 8 character description transmitted in two 10A group
> + * block 2 bit0 is PTYN segment address.
> + * block3 and block4 is PTYN text character
> + */
> +void rds_get_ptyn(unsigned char *buf)
> +{
> +       unsigned char *blk_2 = buf + rds_data_unit_size;
> +       unsigned char *blk_head[2];
> +       unsigned char seg_addr = ((*(blk_2 + 2)) & 0x01);
> +       unsigned char ptyn[4], i, step;
> +       unsigned char *blkc = buf + 2 * rds_data_unit_size;
> +       unsigned char *blkd = buf + 2 * rds_data_unit_size;
> +
> +       blk_head[0] = buf + 2 * rds_data_unit_size;
> +       blk_head[1] = buf + 3 * rds_data_unit_size;
> +       memcpy((void *)&ptyn[0], (void *)(blk_head[0] + 1), 2);
> +       memcpy((void *)&ptyn[2], (void *)(blk_head[1] + 1), 2);
> +       for (i = 0; i < 2; i++) {
> +               step = i >> 1;
> +               /* update seg_addr[0,1] if blockC/D is reliable data */
> +               if ((*blkc == 1) && (*blkd == 1)) {
> +                       /* it's a new PTYN */
> +                       if (memcmp((void *)&ptyn[seg_addr * 4 + step], (void *)
> +                               (ptyn + step), 2) != 0)
> +                               memcpy((void *)&ptyn[seg_addr * 4 + step],
> +                               (void *)(ptyn + step), 2);
> +               }
> +       }
> +}
> +
> +/*
> + * EWS = Coding of Emergency Warning Systems
> + * EWS inclued belows:
> + * unsigned char data_5b;
> + * unsigned short data_16b_1;
> + * unsigned short data_16b_2;
> + */
> +void rds_get_ews(unsigned char *buf)
> +{
> +       unsigned char data_5b;
> +       unsigned short data_16b_1;
> +       unsigned short data_16b_2;
> +       unsigned char *blk_2 = buf + rds_data_unit_size;
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +       unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
> +
> +       data_5b = (unsigned char)((*(blk_2 + 2)) & 0x1F);
> +       bytes_to_short(data_16b_1, (blk_3 + 1));
> +       bytes_to_short(data_16b_2, (blk_4 + 1));
> +}
> +
> +void rfd_get_rtplus(unsigned char *buf)
> +{
> +       unsigned char   *blk_b = buf + rds_data_unit_size;
> +       unsigned char   *blk_c = buf + 2 * rds_data_unit_size;
> +       unsigned char   *blk_d = buf + 3 * rds_data_unit_size;
> +       unsigned char   content_type, s_marker, l_marker;
> +       bool running;
> +
> +       running = ((*(blk_b + 2) & 0x08) != 0) ? 1 : 0;
> +       if ((*blk_c == 1) && (*blk_b == 1)) {
> +               content_type = ((*(blk_b + 2) & 0x07) << 3) + (*(blk_c + 1)
> +                       >> 5);
> +               s_marker = (((*(blk_c + 1) & 0x1F) << 1) + (*(blk_c + 2)
> +                       >> 7));
> +               l_marker = (((*(blk_c + 2)) & 0x7F) >> 1);
> +       }
> +       if ((*blk_c == 1) && (*blk_d == 1)) {
> +               content_type = ((*(blk_c + 2) & 0x01) << 5) +
> +                       (*(blk_d + 1) >> 3);
> +               s_marker = (*(blk_d + 2) >> 5) + ((*(blk_d + 1) & 0x07) << 3);
> +               l_marker = (*(blk_d + 2) & 0x1f);
> +       }
> +}
> +
> +/* ODA = Open Data Applications */
> +void rds_get_oda(unsigned char *buf)
> +{
> +       rfd_get_rtplus(buf);
> +}
> +
> +/* TDC = Transparent Data Channel */
> +void rds_get_tdc(unsigned char *buf, unsigned char version)
> +{
> +       /* 2nd  block */
> +       unsigned char   *blk_b  = buf + rds_data_unit_size;
> +       /* 3rd block */
> +       unsigned char   *blk_c  = buf + 2*rds_data_unit_size;
> +       /* 4rd block */
> +       unsigned char   *blk_d  = buf + 3*rds_data_unit_size;
> +       unsigned char chnl_num, len, tdc_seg[4];
> +       /* unrecoverable block 3,or ERROR in block 4, discard this group */
> +       if ((*blk_b == 0) || (*blk_c == 0) || (*blk_d == 0))
> +               return;
> +
> +       /* read TDChannel number */
> +       chnl_num = *(blk_b + 2) & 0x1f;
> +       if (version == grp_ver_a) {
> +               memcpy(tdc_seg, blk_c + 1, 2);
> +               len = 2;
> +       }
> +
> +       memcpy(tdc_seg +  len, blk_d + 1, 2);
> +       len += 2;
> +}
> +
> +/* CT = Programe Clock time */
> +void rds_get_ct(unsigned char *buf)
> +{
> +       unsigned char *blk_2 = buf + rds_data_unit_size;
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +       unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
> +       unsigned char b3_1 = *(blk_3 + 1), b3_2 = *(blk_3 + 2);
> +       unsigned char b4_1 = *(blk_4 + 1), b4_2 = *(blk_4 + 2);
> +       unsigned int temp1, temp2;
> +
> +       unsigned int day = 0;
> +       unsigned char hour, minute, sense, offset;
> +
> +       if ((*(blk_3) == 0) || (*(blk_4) == 0))
> +               return;
> +       temp1 = (unsigned int) ((b3_1 << 8) | b3_2);
> +       temp2 = (unsigned int) (*(blk_2 + 2) & 0x03);
> +       day = (temp2 << 15) | (temp1 >> 1);
> +
> +       temp1 = (unsigned int)(b3_2 & 0x01);
> +       temp2 = (unsigned int)(b4_1 & 0xF0);
> +       hour = (unsigned char)((temp1 << 4) | (temp2 >> 4));
> +       minute = ((b4_1 & 0x0F) << 2) | ((b4_2 & 0xC0) >> 6);
> +       sense = (b4_2 & 0x20) >> 5;
> +       offset = b4_2 & 0x1F;
> +       /* set RDS EVENT FLAG  in here */
> +       fmdev->rds_data.CT.day = day;
> +       fmdev->rds_data.CT.hour = hour;
> +       fmdev->rds_data.CT.minute = minute;
> +       fmdev->rds_data.CT.local_time_offset_half_hour = offset;
> +       fmdev->rds_data.CT.local_time_offset_signbit = sense;
> +}
> +
> +void rds_get_oda_aid(unsigned char *buf)
> +{
> +}
> +
> +/*
> + * rt == Radio Text
> + * Group types which contain this information: 2A 2B
> + * 2A: address in block2 last 4bits, Text in block3 and block4
> + * 2B: address in block2 last 4bits, Text in block4(16bits)
> + */
> +void rds_get_rt(unsigned char *buf, unsigned char grp_type)
> +{
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +       unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
> +       unsigned char addr = ((*(buf + rds_data_unit_size + 2)) & 0x0F);
> +       unsigned char text_flag = ((*(buf + rds_data_unit_size + 2)) & 0x10);
> +
> +       pr_info("RT Text A/B Flag is %d\n", text_flag);
> +
> +       /* add for RT not support two types*/
> +       if (text_flag != 0)
> +               return;
> +       if (grp_type == 0x2A) {
> +               if (*(blk_3 + 1) == 0x0d)
> +                       *(blk_3 + 1) = '\0';
> +               if (*(blk_3 + 2) == 0x0d)
> +                       *(blk_3 + 2) = '\0';
> +               if (*(blk_4 + 1) == 0x0d)
> +                       *(blk_4 + 1) = '\0';
> +               if (*(blk_4 + 2) == 0x0d)
> +                       *(blk_4 + 2) = '\0';
> +               fmdev->rds_data.rt_data.textdata[3][addr * 4] = *(blk_3 + 1);
> +               fmdev->rds_data.rt_data.textdata[3][addr * 4 + 1] =
> +                       *(blk_3 + 2);
> +               fmdev->rds_data.rt_data.textdata[3][addr * 4 + 2] =
> +                       *(blk_4 + 1);
> +               fmdev->rds_data.rt_data.textdata[3][addr * 4 + 3] =
> +                       *(blk_4 + 2);
> +       }
> +       /* group type = 2B */
> +       else {
> +               if (*(blk_3 + 1) == 0x0d)
> +                       *(blk_3 + 1) = '\0';
> +               if (*(blk_3 + 2) == 0x0d)
> +                       *(blk_3 + 2) = '\0';
> +               fmdev->rds_data.rt_data.textdata[3][addr * 2] = *(blk_3 + 1);
> +               fmdev->rds_data.rt_data.textdata[3][addr * 2 + 1] =
> +                       *(blk_3 + 2);
> +       }
> +       rds_event_set(&(fmdev->rds_data.event_status),
> +               RDS_EVENT_LAST_RADIOTEXT);
> +       pr_info("RT is %s\n", fmdev->rds_data.rt_data.textdata[3]);
> +}
> +
> +/* PIN = Programme Item Number */
> +
> +void rds_get_pin(unsigned char *buf)
> +{
> +       struct RDS_PIN {
> +               unsigned char day;
> +               unsigned char hour;
> +               unsigned char minute;
> +       } rds_pin;
> +
> +       unsigned char *blk_4 = buf + 3 * rds_data_unit_size;
> +       unsigned char byte1 = *(blk_4 + 1), byte2 = *(blk_4 + 2);
> +
> +       if (*blk_4 == 0)
> +               return;
> +       rds_pin.day = ((byte1 & 0xF8) >> 3);
> +       rds_pin.hour = (byte1 & 0x07) << 2 | ((byte2 & 0xC0) >> 6);
> +       rds_pin.minute = (byte2 & 0x3F);
> +}
> +
> +/*
> + * SLC = Slow Labelling codes from group 1A, block3
> + * LA 0 0 0 OPC ECC
> + */
> +
> +void rds_get_slc(unsigned char *buf)
> +{
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +       unsigned char variant_code, slc_type,  paging;
> +       unsigned char ecc_code = 0;
> +       unsigned short data;
> +
> +       if ((*blk_3) == 0)
> +               return;
> +       bytes_to_short(data, blk_3);
> +       data &= 0x0FFF;
> +       /* take bit12 ~ bit14 of block3 as variant code */
> +       variant_code = ((*(blk_3 + 1) & 0x70) >> 4);
> +       if ((variant_code == 0x04) || (variant_code == 0x05))
> +               slc_type = 0x04;
> +       else
> +               slc_type = variant_code;
> +       if (slc_type == 0) {
> +               ecc_code = *(blk_3 + 2);
> +               paging = (*(blk_3 + 1) & 0x0f);
> +       }
> +       fmdev->rds_data.extend_country_code = ecc_code;
> +}
> +
> +/*
> + * Group types which contain this information: 0A 0B
> + * PS = Programme Service name
> + * block2 last 2bit stard for address, block4 16bits meaning ps.
> + */
> +
> +void rds_get_ps(unsigned char *buf)
> +{
> +       unsigned char *blk_2 = buf + rds_data_unit_size;
> +       unsigned char *blk_4 = buf + 3 *  rds_data_unit_size;
> +       unsigned char index = (unsigned char)((*(blk_2 + 2) & 0x03) * 2);
> +
> +       pr_info("PS start receive\n");
> +       pr_info("blk2 =%d, blk4=%d\n", *blk_2, *blk_4);
> +       if ((*blk_2) == 1) {
> +               if ((flag_next == 0) && (index == 0)) {
> +                       memcpy(fmdev->rds_data.ps_data.PS[3],
> +                               fmdev->rds_data.ps_data.PS[2], 8);
> +                       pr_info("PS is %s\n", fmdev->rds_data.ps_data.PS[3]);
> +                       if (fmdev->rds_data.ps_data.PS[3] != NULL)
> +                               rds_event_set(&(fmdev->rds_data.event_status),
> +                                       RDS_EVENT_PROGRAMNAME);
> +                       memset(fmdev->rds_data.ps_data.PS[2], 0x0, 8);
> +               }
> +               if (flag_next == 1)
> +                       flag_next = 0;
> +
> +               fmdev->rds_data.ps_data.addr_cnt = index;
> +               fmdev->rds_data.ps_data.PS[2][index] = *(blk_4 + 1);
> +               fmdev->rds_data.ps_data.PS[2][index + 1] = *(blk_4 + 2);
> +       }
> +       pr_info("the PS index is %x\n", index);
> +       pr_info("The event is %x\n", fmdev->rds_data.event_status);
> +       pr_info("The PS is %s\n", fmdev->rds_data.ps_data.PS[3]);
> +       pr_info("blk4+1=0x%x\n", *(blk_4 + 1));
> +       pr_info("blk4+2=0x%x\n", *(blk_4 + 2));
> +
> +}
> +unsigned short rds_get_freq(void)
> +{
> +       return 0;
> +}
> +void rds_get_af_method(unsigned char AFH, unsigned char AFL)
> +{
> +       static signed short pre_af_num;
> +       unsigned char  indx, indx2, num;
> +
> +       pr_info("af code is %d and %d\n", AFH, AFL);
> +       if (AFH >= RDS_AF_NUM_1 && AFH <= RDS_AF_NUM_25) {
> +               if (AFH == RDS_AF_NUM_1) {
> +                       fmdev->rds_data.af_data.ismethod_a = RDS_AF_M_A;
> +                       fmdev->rds_data.af_data.AF_NUM = 1;
> +               }
> +               /* have got af number */
> +               fmdev->rds_data.af_data.isafnum_get = 0;
> +               pre_af_num = AFH - 224;
> +               if (pre_af_num != fmdev->rds_data.af_data.AF_NUM)
> +                       fmdev->rds_data.af_data.AF_NUM = pre_af_num;
> +               else
> +                       fmdev->rds_data.af_data.isafnum_get = 1;
> +               if ((AFL < 205) && (AFL > 0)) {
> +                       fmdev->rds_data.af_data.AF[0][0] = AFL + 875;
> +                       /* convert to 100KHz */
> +#ifdef SPRD_FM_50KHZ_SUPPORT
> +                       fmdev->rds_data.af_data.AF[0][0] *= 10;
> +#endif
> +                       if ((fmdev->rds_data.af_data.AF[0][0]) !=
> +                               (fmdev->rds_data.af_data.AF[1][0])) {
> +                               fmdev->rds_data.af_data.AF[1][0] =
> +                                       fmdev->rds_data.af_data.AF[0][0];
> +                       } else {
> +                               if (fmdev->rds_data.af_data.AF[1][0] !=
> +                                       rds_get_freq())
> +                                       fmdev->rds_data.af_data.ismethod_a = 1;
> +                               else
> +                                       fmdev->rds_data.af_data.ismethod_a = 0;
> +                       }
> +
> +                       /* only one AF handle */
> +                       if ((fmdev->rds_data.af_data.isafnum_get) &&
> +                               (fmdev->rds_data.af_data.AF_NUM == 1)) {
> +                               fmdev->rds_data.af_data.addr_cnt = 0xFF;
> +                       }
> +               }
> +       } else if ((fmdev->rds_data.af_data.isafnum_get) &&
> +               (fmdev->rds_data.af_data.addr_cnt != 0xFF)) {
> +               /* AF Num correct */
> +               num = fmdev->rds_data.af_data.AF_NUM;
> +               num = num >> 1;
> +               /*
> +                * Put AF freq fm_s32o buffer and check if AF
> +                * freq is repeat again
> +                */
> +               for (indx = 1; indx < (num + 1); indx++) {
> +                       if ((AFH == (fmdev->rds_data.af_data.AF[0][2*num-1]))
> +                               && (AFL ==
> +                               (fmdev->rds_data.af_data.AF[0][2*indx]))) {
> +                               pr_info("AF same as\n");
> +                               break;
> +                       } else if (!(fmdev->rds_data.af_data.AF[0][2 * indx-1])
> +                               ) {
> +                               /* convert to 100KHz */
> +                               fmdev->rds_data.af_data.AF[0][2*indx-1] =
> +                                       AFH + 875;
> +                               fmdev->rds_data.af_data.AF[0][2*indx] =
> +                                       AFL + 875;
> +#ifdef MTK_FM_50KHZ_SUPPORT
> +                               fmdev->rds_data.af_data.AF[0][2*indx-1] *= 10;
> +                               fmdev->rds_data.af_data.AF[0][2*indx] *= 10;
> +#endif
> +                               break;
> +                       }
> +               }
> +               num = fmdev->rds_data.af_data.AF_NUM;
> +               if (num <= 0)
> +                       return;
> +               if ((fmdev->rds_data.af_data.AF[0][num-1]) == 0)
> +                       return;
> +               num = num >> 1;
> +               for (indx = 1; indx < num; indx++) {
> +                       for (indx2 = indx + 1; indx2 < (num + 1); indx2++) {
> +                               AFH = fmdev->rds_data.af_data.AF[0][2*indx-1];
> +                               AFL = fmdev->rds_data.af_data.AF[0][2*indx];
> +                               if (AFH > (fmdev->rds_data.af_data.AF[0][2*indx2
> +                                       -1])) {
> +                                       fmdev->rds_data.af_data.AF[0][2*indx-1]
> +                                       = fmdev->rds_data.af_data.AF[0][2
> +                                       *indx2-1];
> +                                       fmdev->rds_data.af_data.AF[0][2*indx] =
> +                                       fmdev->rds_data.af_data.AF[0][2*indx2];
> +                                       fmdev->rds_data.af_data.AF[0][2*indx2-1]
> +                                               = AFH;
> +                                       fmdev->rds_data.af_data.AF[0][2*indx2]
> +                                               = AFL;
> +                               } else if (AFH == (fmdev->rds_data.af_data
> +                                       .AF[0][2*indx2-1])) {
> +                                       if (AFL > (fmdev->rds_data.af_data.AF[0]
> +                                               [2*indx2])) {
> +                                               fmdev->rds_data.af_data.AF[0][2
> +                                                       *indx-1]
> +                                               = fmdev->rds_data.af_data
> +                                               .AF[0][2*indx2-1];
> +                                               fmdev->rds_data.af_data.AF[0][2
> +                                                       *indx] = fmdev->rds_data
> +                                                       .af_data.AF[0][2*indx2];
> +                                               fmdev->rds_data.af_data.AF[0][2*
> +                                                       indx2-1] = AFH;
> +                                               fmdev->rds_data.af_data.AF[0][2
> +                                                       *indx2] = AFL;
> +                                       }
> +                               }
> +                       }
> +               }
> +
> +               /*
> +                * arrange frequency from low to high:end
> +                * compare AF buff0 and buff1 data:start
> +                */
> +               num = fmdev->rds_data.af_data.AF_NUM;
> +               indx2 = 0;
> +               for (indx = 0; indx < num; indx++) {
> +                       if ((fmdev->rds_data.af_data.AF[1][indx]) ==
> +                               (fmdev->rds_data.af_data.AF[0][indx])) {
> +                               if (fmdev->rds_data.af_data.AF[1][indx] != 0)
> +                                       indx2++;
> +                               } else {
> +                                       fmdev->rds_data.af_data.AF[1][indx] =
> +                                       fmdev->rds_data.af_data.AF[0][indx];
> +                               }
> +                       }
> +
> +               /* compare AF buff0 and buff1 data:end */
> +               if (indx2 == num) {
> +                       fmdev->rds_data.af_data.addr_cnt = 0xFF;
> +                       for (indx = 0; indx < num; indx++) {
> +                               if ((fmdev->rds_data.af_data.AF[1][indx])
> +                                       == 0)
> +                                       fmdev->rds_data.af_data.addr_cnt = 0x0F;
> +                       }
> +               } else
> +                       fmdev->rds_data.af_data.addr_cnt = 0x0F;
> +       }
> +}
> +/*
> + * Group types which contain this information: 0A
> + * AF = Alternative Frequencies
> + * af information in block 3
> + */
> +
> +void rds_get_af(unsigned char *buf)
> +{
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +
> +       if (*blk_3 != 1)
> +               return;
> +       rds_get_af_method(*(blk_3 + 1), *(blk_3 + 2));
> +       fmdev->rds_data.af_data.AF[1][24] = 0;
> +}
> +
> +/* Group types which contain this information: 0A 0B 15B */
> +void rds_get_di_ms(unsigned char *buf)
> +{
> +}
> +
> +/*
> + * Group types which contain this information: TP_all(byte1 bit2);
> + * TA: 0A 0B 14B 15B(byte2 bit4)
> + * TP = Traffic Program identification; TA = Traffic Announcement
> + */
> +
> +void rds_get_tp_ta(unsigned char *buf, unsigned char grp_type)
> +{
> +       unsigned char *blk_2 = buf + rds_data_unit_size;
> +       unsigned char byte1 = *(blk_2 + 1), byte2 = *(blk_2 + 2);
> +       unsigned char ta_tp;
> +       unsigned short *event = &(fmdev->rds_data.event_status);
> +
> +       if ((*blk_2) == 0)
> +               return;
> +       ta_tp = (unsigned char)((byte1 & (1<<2))>>2);
> +       if (grp_type == 0x0a || grp_type == 0x0B || grp_type == 0xFB) {
> +               ta_tp |= (byte2 & (1 << 4));
> +               rds_event_set(event, RDS_EVENT_TAON_OFF);
> +       }
> +}
> +
> +/*
> + * Group types which contain this information: all
> + * block2:Programme Type code = 5 bits($)
> + * #### ##$$ $$$# ####
> + */
> +
> +void rds_get_pty(unsigned char *buf)
> +{
> +       unsigned char *blk_2 = buf + rds_data_unit_size;
> +       unsigned char byte1 = *(blk_2 + 1), byte2 = *(blk_2 + 2);
> +       unsigned char   pty = 0;
> +
> +       if ((*blk_2) == 1)
> +               pty = ((byte2 >> 5) | ((byte1 & 0x3) << 3));
> +       fmdev->rds_data.PTY = pty;
> +}
> +
> +/*
> + * Group types which contain this information: all
> + * Read PI code from the group. grp_typeA: block 1 and block3,
> + * grp_type B: block3
> + */
> +
> +void rds_get_pi_code(unsigned char *buf, unsigned char version)
> +{
> +       unsigned char *blk_3 = buf + 2 * rds_data_unit_size;
> +       /* pi_code for version A, pi_code_b for version B */
> +       unsigned short pi_code = 0, pi_code_b = 0;
> +       unsigned char crc_flag1 = *buf;
> +       unsigned char crc_flag3 = *(buf + 2 * rds_data_unit_size);
> +
> +       if (version == invalid_grp_type)
> +               return;
> +
> +       if (crc_flag1 == 1)
> +               bytes_to_short(pi_code, buf+1);
> +       else
> +               return;
> +
> +       if (version == grp_ver_b) {
> +               if (crc_flag3 == 1)
> +                       bytes_to_short(pi_code_b, blk_3 + 1);
> +       }
> +
> +       if (pi_code == 0 && pi_code_b != 0)
> +               pi_code = pi_code_b;
> +/* send pi_code value to global and copy to user space in read rds interface */
> +       fmdev->rds_data.PI = pi_code;
> +}
> +
> +/*
> + * Block 1: PIcode(16bit)+CRC
> + * Block 2 : Group type code(4bit)
> + * B0 version(1bit 0:version A; 1:version B)
> + * TP(1bit)+ PTY(5 bits)
> + * @ buffer point to the start of Block 1
> + * Block3: 16bits + 10bits
> + * Block4: 16bits + 10bits
> + * rds_get_group_type from Block2
> + */
> +unsigned char rds_get_group_type(unsigned char *buffer)
> +{
> +       unsigned char *crc_blk_2 = buffer + rds_data_unit_size;
> +       unsigned char blk2_byte1 = *(crc_blk_2+1);
> +       unsigned char group_type;
> +       unsigned char crc_flag = *crc_blk_2;
> +
> +       if (crc_flag == 1)
> +               group_type = (blk2_byte1 & grp_type_mask);
> +       else
> +               group_type = invalid_grp_type;
> +       /* 0:version A, 1: version B */
> +       if (blk2_byte1 & grp_ver_bit)
> +               group_type |= grp_ver_b;
> +       else
> +               group_type |= grp_ver_a;
> +
> +       return group_type;
> +}
> +
> +void dump_rx_data(unsigned char *buffer, unsigned int len)
> +{
> +       char i;
> +
> +       pr_info("\n fm rx data(%d): ", len);
> +       for (i = 0; i < len; i++)
> +               pr_info("0x%x__", *(buffer+i));
> +       pr_info("\n");
> +}
> +
> +/*
> + * rds_parser
> + * Block0: PI code(16bits)
> + * Block1: Group type(4bits), B0=version code(1bit),
> + * TP=traffic program code(1bit),
> + * PTY=program type code(5bits), other(5bits)
> + * @getfreq - function pointer, AF need get current freq
> + * Theoretically From FIFO :
> + * One Group = Block1(16 bits) + CRC(10 bits)
> + * Block2 +CRC(10 bits)
> + * Block3(16 bits) + CRC(10 bits)
> + * Block4(16 bits) + CRC(10 bits)
> + * From marlin2 chip, the data stream is like below:
> + * One Group = CRC_Flag(8bit)+Block1(16bits)
> + * CRC_Flag(8bit)+Block2(16bits)
> + * CRC_Flag(8bit)+Block3(16bits)
> + * CRC_Flag(8bit)+Block4(16bits)
> + */
> +void rds_parser(unsigned char *buffer, unsigned char len, unsigned int fifo_id)
> +{
> +       unsigned char grp_type;
> +
> +       dump_rx_data(buffer, len);
> +       grp_type = rds_get_group_type(buffer);
> +       pr_info("group type is : 0x%x\n", grp_type);
> +
> +       rds_get_pi_code(buffer, grp_type & grp_ver_mask);
> +       rds_get_pty(buffer);
> +       rds_get_tp_ta(buffer, grp_type);
> +
> +       switch (grp_type) {
> +       case invalid_grp_type:
> +               pr_info("invalid group type\n");
> +               break;
> +       /* Processing group 0A */
> +       case 0x0A:
> +               rds_get_di_ms(buffer);
> +               rds_get_af(buffer);
> +               rds_get_ps(buffer);
> +               break;
> +       /* Processing group 0B */
> +       case 0x0B:
> +               rds_get_di_ms(buffer);
> +               rds_get_ps(buffer);
> +               break;
> +       case 0x1A:
> +               rds_get_slc(buffer);
> +               rds_get_pin(buffer);
> +               break;
> +       case 0x1B:
> +               rds_get_pin(buffer);
> +               break;
> +       case 0x2A:
> +       case 0x2B:
> +               rds_get_rt(buffer, grp_type);
> +               break;
> +       case 0x3A:
> +               rds_get_oda_aid(buffer);
> +               break;
> +       case 0x4A:
> +               rds_get_ct(buffer);
> +               break;
> +       case 0x5A:
> +       case 0x5B:
> +               rds_get_tdc(buffer, grp_type & grp_ver_mask);
> +               break;
> +       case 0x9a:
> +               rds_get_ews(buffer);
> +               break;
> +       /* 10A group */
> +       case 0xAA:
> +               rds_get_ptyn(buffer);
> +               break;
> +       case 0xEA:
> +               rds_get_eon(buffer);
> +               break;
> +       case 0xEB:
> +               rds_get_eon_ta(buffer);
> +               break;
> +       case 0xFB:
> +               rds_get_di_ms(buffer);
> +               break;
> +/* ODA (Open Data Applications) group availability signaled in type 3A groups */
> +       case 0x3B:
> +       case 0x4B:
> +       case 0x6A:
> +       case 0x6B:
> +       case 0x7A:
> +       case 0x7B:
> +       case 0x8A:
> +       case 0x8B:
> +       case 0x9B:
> +       case 0xAB:
> +       case 0xBA:
> +       case 0xBB:
> +       case 0xCA:
> +       case 0xCB:
> +       case 0xDB:
> +       case 0xDA:
> +       case 0xFA:
> +               rds_get_oda(buffer);
> +               break;
> +       default:
> +               pr_info("rds group type[0x%x] not to be processed\n", grp_type);
> +               break;
> +       }
> +       sdiom_pt_read_release(fifo_id);
> +       pr_info("fmdrv release fifo_id is %d\n", fifo_id);
> +}
> +
> diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h
> new file mode 100644
> index 0000000..404dc28
> --- /dev/null
> +++ b/drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h
> @@ -0,0 +1,103 @@
> +/*
> + * SPREADTRUM SC2342 FM Radio driver
> + *
> + * Copyright (C) 2015~2017 Spreadtrum, Inc.
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
> +
> +#ifndef _FMDRV_RDS_PARSER
> +#define _FMDRV_RDS_PARSER
> +
> +/* Block1 */
> +#define RDS_BLCKA              0x00
> +/* Block2 */
> +#define RDS_BLCKB              0x10
> +/* Block3 */
> +#define RDS_BLCKC              0x20
> +/* Block4 */
> +#define RDS_BLCKD              0x30
> +/* BlockC hyphen */
> +#define RDS_BLCKC_C            0x40
> +/* BlockE in RBDS */
> +#define RDS_BLCKE_B            0x50
> +/* Block E  */
> +#define RDS_BLCKE              0x60
> +
> +/* 3bytes = 8bit(CRC flag) + 16bits (1 block ) */
> +#define rds_data_unit_size     3
> +#define rds_data_group_size    (3*4)
> +#define grp_type_mask          0xF0
> +#define grp_ver_mask           0x0F
> +/* 0:version A, 1: version B */
> +#define grp_ver_bit            (0x01<<3)
> +#define grp_ver_a              0x0A
> +#define grp_ver_b              0x0B
> +#define invalid_grp_type       0x00
> +
> +/* AF fill in code */
> +#define RDS_AF_FILL            205
> +/* AF invalid code low marker */
> +#define RDS_AF_INVAL_L         205
> +/* AF invalid code middle marker */
> +#define RDS_AF_INVAL_M         223
> +/* 0 AF follow */
> +#define RDS_AF_NONE            224
> +/* 1 AF follow */
> +#define RDS_AF_NUM_1           225
> +/* 25 AFs follow */
> +#define RDS_AF_NUM_25          249
> +/* LF/MF follow */
> +#define RDS_LF_MF              250
> +/* AF invalid code high marker */
> +#define RDS_AF_INVAL_H         251
> +/* AF invalid code top marker */
> +#define RDS_AF_INVAL_T         255
> +/* lowest MF frequency */
> +#define RDS_MF_LOW             0x10
> +
> +/* FM base frequency */
> +#define RDS_FM_BASE            875
> +/* MF base frequency */
> +#define RDS_MF_BASE            531
> +/* LF base frequency */
> +#define RDS_LF_BASE            153
> +
> +/* minimum day */
> +#define RDS_MIN_DAY            1
> +/* maximum day */
> +#define RDS_MAX_DAY            31
> +/* minimum hour */
> +#define RDS_MIN_HUR            0
> +/* maximum hour */
> +#define RDS_MAX_HUR            23
> +/* minimum minute */
> +#define RDS_MIN_MUT            0
> +/* maximum minute */
> +#define RDS_MAX_MUT            59
> +/* left over rds data length max in control block */
> +#define BTA_RDS_LEFT_LEN         24
> +/* Max radio text length */
> +#define BTA_RDS_RT_LEN           64
> +/* 8 character RDS feature length, i.e. PS, PTYN */
> +#define BTA_RDS_LEN_8            8
> +
> +/* AF encoding method */
> +enum {
> +       /* unknown */
> +       RDS_AF_M_U,
> +       /* method - A */
> +       RDS_AF_M_A,
> +       /* method - B */
> +       RDS_AF_M_B
> +};
> +
> +/* change 8 bits to 16bits */
> +#define bytes_to_short(dest, src)  (dest = (unsigned short)(((unsigned short)\
> +       (*(src)) << 8) + (unsigned short)(*((src) + 1))))
> +
> +void dump_rx_data(unsigned char *buffer, unsigned int len);
> +void rds_parser(unsigned char *buffer, unsigned char len,
> +               unsigned int fifo_id);
> +
> +#endif /* _FMDRV_RDS_PARSER */
> --
> 2.7.4
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux