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