2017-07-04 10:22:17

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH 0/2] add support for Spreadtrum's FM driver

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

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

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

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

arch/arm64/boot/dts/sprd/sp9860g-1h10.dts | 4 +
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/sprd-wcn/Kconfig | 14 +
drivers/misc/sprd-wcn/Makefile | 1 +
drivers/misc/sprd-wcn/radio/Kconfig | 8 +
drivers/misc/sprd-wcn/radio/Makefile | 2 +
drivers/misc/sprd-wcn/radio/fmdrv.h | 595 +++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_main.c | 1245 ++++++++++++++++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_main.h | 117 +++
drivers/misc/sprd-wcn/radio/fmdrv_ops.c | 447 +++++++++
drivers/misc/sprd-wcn/radio/fmdrv_ops.h | 17 +
drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c | 753 ++++++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h | 103 ++
14 files changed, 3308 insertions(+)
create mode 100644 drivers/misc/sprd-wcn/Kconfig
create mode 100644 drivers/misc/sprd-wcn/Makefile
create mode 100644 drivers/misc/sprd-wcn/radio/Kconfig
create mode 100644 drivers/misc/sprd-wcn/radio/Makefile
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h

--
2.7.4


2017-07-04 10:22:06

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH 1/2] arm64: dts: add Spreadtrum's fm support

Added FM support for Spreadtrum's SP9860 board.

Signed-off-by: Songhe Wei <[email protected]>
Signed-off-by: Chunyan Zhang <[email protected]>
---
arch/arm64/boot/dts/sprd/sp9860g-1h10.dts | 4 ++++
1 file changed, 4 insertions(+)

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

&uart0 {
--
2.7.4

2017-07-04 10:22:26

by Chunyan Zhang

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

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 <[email protected]>
Signed-off-by: Chunyan Zhang <[email protected]>
---
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<[email protected]>");
+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

2017-07-04 10:51:52

by Arnd Bergmann

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

On Tue, Jul 4, 2017 at 12:15 PM, Chunyan Zhang
<[email protected]> 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 <[email protected]>
> Signed-off-by: Chunyan Zhang <[email protected]>

(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<[email protected]>");
> +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
>

2017-07-04 12:36:08

by Mark Rutland

[permalink] [raw]
Subject: Re: [PATCH 1/2] arm64: dts: add Spreadtrum's fm support

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

What kind of device is this? MMIO?

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

This will need binding documentation.

Thanks,
Mark.


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

2017-07-05 10:18:59

by Chunyan Zhang

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

On 4 July 2017 at 18:51, Arnd Bergmann <[email protected]> wrote:
> On Tue, Jul 4, 2017 at 12:15 PM, Chunyan Zhang
> <[email protected]> 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 <[email protected]>
>> Signed-off-by: Chunyan Zhang <[email protected]>
>
> (adding linux-media folks to Cc)

(You forgot to add them in :))

>
> Hi Chunyan,

Hi Arnd,

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

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

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

Thanks,
Chunyan

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

2017-07-05 10:31:49

by Chunyan Zhang

[permalink] [raw]
Subject: [PATCH 0/2] add support for Spreadtrum's FM driver

[add linux-media list and Mauro Carvalho Chehab]

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

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

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

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

arch/arm64/boot/dts/sprd/sp9860g-1h10.dts | 4 +
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/sprd-wcn/Kconfig | 14 +
drivers/misc/sprd-wcn/Makefile | 1 +
drivers/misc/sprd-wcn/radio/Kconfig | 8 +
drivers/misc/sprd-wcn/radio/Makefile | 2 +
drivers/misc/sprd-wcn/radio/fmdrv.h | 595 +++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_main.c | 1245 ++++++++++++++++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_main.h | 117 +++
drivers/misc/sprd-wcn/radio/fmdrv_ops.c | 447 +++++++++
drivers/misc/sprd-wcn/radio/fmdrv_ops.h | 17 +
drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c | 753 ++++++++++++++
drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h | 103 ++
14 files changed, 3308 insertions(+)
create mode 100644 drivers/misc/sprd-wcn/Kconfig
create mode 100644 drivers/misc/sprd-wcn/Makefile
create mode 100644 drivers/misc/sprd-wcn/radio/Kconfig
create mode 100644 drivers/misc/sprd-wcn/radio/Makefile
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.h
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c
create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h

--
2.7.4

2017-07-05 11:43:18

by Arnd Bergmann

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

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

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

Arnd