On Tue, Jul 4, 2017 at 12:15 PM, Chunyan Zhang <chunyan.zhang@xxxxxxxxxxxxxx> wrote: > This patch added FM radio driver for Spreadtrum's SC2342, which's > a WCN SoC, also added a new directory for Spreadtrum's WCN SoCs. > > Signed-off-by: Songhe Wei <songhe.wei@xxxxxxxxxxxxxx> > Signed-off-by: Chunyan Zhang <chunyan.zhang@xxxxxxxxxxxxxx> (adding linux-media folks to Cc) Hi Chunyan, Thanks for posting this for inclusion as Greg asked for. I'm not sure what the policy is for new radio drivers, but I assume this would have to go to drivers/staging/media/ as it is a driver for hardware that fits into drivers/media/radio but doesn't use the respective APIs. Arnd --- end of message, full patch quoted for reference below > --- > drivers/misc/Kconfig | 1 + > drivers/misc/Makefile | 1 + > drivers/misc/sprd-wcn/Kconfig | 14 + > drivers/misc/sprd-wcn/Makefile | 1 + > drivers/misc/sprd-wcn/radio/Kconfig | 8 + > drivers/misc/sprd-wcn/radio/Makefile | 2 + > drivers/misc/sprd-wcn/radio/fmdrv.h | 595 +++++++++++ > drivers/misc/sprd-wcn/radio/fmdrv_main.c | 1245 ++++++++++++++++++++++++ > drivers/misc/sprd-wcn/radio/fmdrv_main.h | 117 +++ > drivers/misc/sprd-wcn/radio/fmdrv_ops.c | 447 +++++++++ > drivers/misc/sprd-wcn/radio/fmdrv_ops.h | 17 + > drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c | 753 ++++++++++++++ > drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h | 103 ++ > 13 files changed, 3304 insertions(+) > create mode 100644 drivers/misc/sprd-wcn/Kconfig > create mode 100644 drivers/misc/sprd-wcn/Makefile > create mode 100644 drivers/misc/sprd-wcn/radio/Kconfig > create mode 100644 drivers/misc/sprd-wcn/radio/Makefile > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv.h > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.c > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_main.h > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.c > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_ops.h > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.c > create mode 100644 drivers/misc/sprd-wcn/radio/fmdrv_rds_parser.h > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index 07bbd4c..5e295b3 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -510,4 +510,5 @@ source "drivers/misc/mic/Kconfig" > source "drivers/misc/genwqe/Kconfig" > source "drivers/misc/echo/Kconfig" > source "drivers/misc/cxl/Kconfig" > +source "drivers/misc/sprd-wcn/Kconfig" > endmenu > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index ad13677..df75ea7 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -55,6 +55,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o > obj-$(CONFIG_CXL_BASE) += cxl/ > obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o > obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o > +obj-$(CONFIG_SPRD_WCN) += sprd-wcn/ > > lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o > lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o > diff --git a/drivers/misc/sprd-wcn/Kconfig b/drivers/misc/sprd-wcn/Kconfig > new file mode 100644 > index 0000000..d2e7428 > --- /dev/null > +++ b/drivers/misc/sprd-wcn/Kconfig > @@ -0,0 +1,14 @@ > +config SPRD_WCN > + tristate "Support for Spreadtrum's WCN SoCs" > + depends on ARCH_SPRD > + default n > + help > + This enables Spreadtrum's WCN (wireless connectivity network) > + SoCs. In general, Spreadtrum's WCN SoCs consisted of some > + modules, such as FM, bluetooth, wifi, GPS, etc. > + > +if SPRD_WCN > + > +source "drivers/misc/sprd-wcn/radio/Kconfig" > + > +endif > diff --git a/drivers/misc/sprd-wcn/Makefile b/drivers/misc/sprd-wcn/Makefile > new file mode 100644 > index 0000000..3ad5dad > --- /dev/null > +++ b/drivers/misc/sprd-wcn/Makefile > @@ -0,0 +1 @@ > +obj-y += radio/ > diff --git a/drivers/misc/sprd-wcn/radio/Kconfig b/drivers/misc/sprd-wcn/radio/Kconfig > new file mode 100644 > index 0000000..3cc0f7e > --- /dev/null > +++ b/drivers/misc/sprd-wcn/radio/Kconfig > @@ -0,0 +1,8 @@ > +## Spreadtrum SC2332 FM drivers > + > +config SPRD_RADIO_SC2332 > + tristate "Support for the Spreadtrum Radio SC2332" > + default n > + ---help--- > + Say Y to enable built-in FM radio controller for the > + Spreadtrum SC2332 SoC. > diff --git a/drivers/misc/sprd-wcn/radio/Makefile b/drivers/misc/sprd-wcn/radio/Makefile > new file mode 100644 > index 0000000..16f1582 > --- /dev/null > +++ b/drivers/misc/sprd-wcn/radio/Makefile > @@ -0,0 +1,2 @@ > +obj-$(CONFIG_SPRD_RADIO_SC2332) := marlin2_fm.o > +marlin2_fm-objs := fmdrv_main.o fmdrv_ops.o fmdrv_rds_parser.o > diff --git a/drivers/misc/sprd-wcn/radio/fmdrv.h b/drivers/misc/sprd-wcn/radio/fmdrv.h > new file mode 100644 > index 0000000..e74ff7f > --- /dev/null > +++ b/drivers/misc/sprd-wcn/radio/fmdrv.h > @@ -0,0 +1,595 @@ > +/* > + * SPREADTRUM SC2342 FM Radio driver > + * > + * Copyright (C) 2015~2017 Spreadtrum, Inc. > + * > + * SPDX-License-Identifier: GPL-2.0 > + */ > + > +#ifndef _FM_DRV_H > +#define _FM_DRV_H > + > +#include <linux/completion.h> > +#include <linux/ioctl.h> > +#include <linux/interrupt.h> > +#include <linux/time.h> > + > +#define FM_DEV_NAME "fm" > +#define FM_RDS_ENABLE 0x01 > +#define MARLIN_FM 0 > + > +/* scan sort algorithm */ > +enum { > + FM_SCAN_SORT_NON = 0, > + FM_SCAN_SORT_UP, > + FM_SCAN_SORT_DOWN, > + FM_SCAN_SORT_MAX > +}; > + > +/* scan methods */ > +enum { > + /* select hardware scan, advantage: fast */ > + FM_SCAN_SEL_HW = 0, > + /* select software scan, advantage: more accurate */ > + FM_SCAN_SEL_SW, > + FM_SCAN_SEL_MAX > +}; > + > +/* FM config for customer */ > +/* FM radio long antenna RSSI threshold(11.375dBuV) */ > +#define FMR_RSSI_TH_LONG 0x0301 > +/* FM radio short antenna RSSI threshold(-1dBuV) */ > +#define FMR_RSSI_TH_SHORT 0x02E0 > +/* FM radio Channel quality indicator threshold(0x0000~0x00FF) */ > +#define FMR_CQI_TH 0x00E9 > +/* FM radio seek space,1:100KHZ; 2:200KHZ */ > +#define FMR_SEEK_SPACE 1 > +/* FM radio scan max channel size */ > +#define FMR_SCAN_CH_SIZE 40 > +/* FM radio band, 1:87.5MHz~108.0MHz;*/ > +/* 2:76.0MHz~90.0MHz;*/ > +/* 3:76.0MHz~108.0MHz; 4:special */ > +#define FMR_BAND 1 > +/* FM radio special band low freq(Default 87.5MHz) */ > +#define FMR_BAND_FREQ_L 875 > +/* FM radio special band high freq(Default 108.0MHz) */ > +#define FMR_BAND_FREQ_H 1080 > +#define FM_SCAN_SORT_SELECT FM_SCAN_SORT_NON > +#define FM_SCAN_SELECT FM_SCAN_SEL_HW > +/* soft-mute threshold when software scan, rang: 0~3, */ > +/* 0 means better audio quality but less channel */ > +#define FM_SCAN_SOFT_MUTE_GAIN_TH 3 > +/* rang: -102 ~ -72 */ > +#define FM_CHIP_DESE_RSSI_TH (-102) > + > +/* FM config for engineer */ > +/* FM radio MR threshold */ > +#define FMR_MR_TH 0x01BD > +/* scan thrshold register */ > +#define ADDR_SCAN_TH 0xE0 > +/* scan CQI register */ > +#define ADDR_CQI_TH 0xE1 > +/* 4 sec */ > +#define FM_DRV_TX_TIMEOUT (4*HZ) > +/* 20 sec */ > +#define FM_DRV_RX_SEEK_TIMEOUT (20*HZ) > + > +/* errno */ > +#define FM_SUCCESS 0 > +#define FM_FAILED 1 > +#define FM_EPARM 2 > +#define FM_BADSTATUS 3 > +#define FM_TUNE_FAILED 4 > +#define FM_SEEK_FAILED 5 > +#define FM_BUSY 6 > +#define FM_SCAN_FAILED 7 > + > +/* band */ > +#define FM_BAND_UNKNOWN 0 > +/* US/Europe band 87.5MHz ~ 108MHz (DEFAULT) */ > +#define FM_BAND_UE 1 > +/* Japan band 76MHz ~ 90MHz */ > +#define FM_BAND_JAPAN 2 > +/* Japan wideband 76MHZ ~ 108MHz */ > +#define FM_BAND_JAPANW 3 > +/* special band between 76MHZ and 108MHz */ > +#define FM_BAND_SPECIAL 4 > +#define FM_BAND_DEFAULT FM_BAND_UE > + > +#define FM_UE_FREQ_MIN 875 > +#define FM_UE_FREQ_MAX 1080 > +#define FM_JP_FREQ_MIN 760 > +#define FM_JP_FREQ_MAX 1080 > +#define FM_FREQ_MIN FMR_BAND_FREQ_L > +#define FM_FREQ_MAX FMR_BAND_FREQ_H > +#define FM_RAIDO_BAND FM_BAND_UE > + > +/* space */ > +#define FM_SPACE_UNKNOWN 0 > +#define FM_SPACE_100K 1 > +#define FM_SPACE_200K 2 > +#define FM_SPACE_50K 5 > +#define FM_SPACE_DEFAULT FM_SPACE_100K > + > +#define FM_SEEK_SPACE FMR_SEEK_SPACE > + > +/* max scan channel num */ > +#define FM_MAX_CHL_SIZE FMR_SCAN_CH_SIZE > +/* auto HiLo */ > +#define FM_AUTO_HILO_OFF 0 > +#define FM_AUTO_HILO_ON 1 > + > +/* seek direction */ > +#define FM_SEEK_UP 0 > +#define FM_SEEK_DOWN 1 > + > +#define FM_VERSION "v0.0" > + > +/* seek threshold */ > +#define FM_SEEKTH_LEVEL_DEFAULT 4 > + > +struct fm_tune_parm { > + uint8_t err; > + uint8_t band; > + uint8_t space; > + uint8_t hilo; > + uint16_t freq; > +}; > + > +struct fm_seek_parm { > + uint8_t err; > + uint8_t band; > + uint8_t space; > + uint8_t hilo; > + uint8_t seekdir; > + uint8_t seekth; > + uint16_t freq; > +}; > + > +/* Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI */ > +/* Frequency_Offset_Th [0x0000 0xFFFF] EXPERIENCE VALUES:0x5dc */ > +/* Pilot_Power_Th RANGES: [0x0000 0x1FFF] EXPERIENCE VALUES:0x190 */ > +/* Noise_Power_Th RANGES: [0x0000 0x1FFF] EXPERIENCE VALUES:0xB0 */ > +struct fm_seek_criteria_parm { > + unsigned char rssi_th; > + unsigned char snr_th; > + unsigned short freq_offset_th; > + unsigned short pilot_power_th; > + unsigned short noise_power_th; > +} __packed; > + > +struct fm_audio_threshold_parm { > + unsigned short hbound; > + unsigned short lbound; > + unsigned short power_th; > + unsigned char phyt; > + unsigned char snr_th; > +} __packed; > +/*__attribute__ ((packed));*/ > + > +struct fm_reg_ctl_parm { > + unsigned char err; > + unsigned int addr; > + unsigned int val; > + /*0:write, 1:read*/ > + unsigned char rw_flag; > +} __packed; > + > +struct fm_scan_parm { > + uint8_t err; > + uint8_t band; > + uint8_t space; > + uint8_t hilo; > + uint16_t freq; > + uint16_t scantbl[16]; > + uint16_t scantblsize; > +}; > + > +struct fm_scan_all_parm { > + unsigned char band;/*87.5~108,76~*/ > + unsigned char space;/*50 or 100KHz */ > + unsigned char chanel_num; > + unsigned short freq[36]; /* OUT parameter*/ > +}; > + > +struct fm_ch_rssi { > + uint16_t freq; > + int rssi; > +}; > + > +enum fm_scan_cmd_t { > + FM_SCAN_CMD_INIT = 0, > + FM_SCAN_CMD_START, > + FM_SCAN_CMD_GET_NUM, > + FM_SCAN_CMD_GET_CH, > + FM_SCAN_CMD_GET_RSSI, > + FM_SCAN_CMD_GET_CH_RSSI, > + FM_SCAN_CMD_MAX > +}; > + > +struct fm_rssi_req { > + uint16_t num; > + uint16_t read_cnt; > + struct fm_ch_rssi cr[16*16]; > +}; > + > +struct fm_hw_info { > + int chip_id; > + int eco_ver; > + int rom_ver; > + int patch_ver; > + int reserve; > +}; > + > +struct rdslag { > + uint8_t TP; > + uint8_t TA; > + uint8_t music; > + uint8_t stereo; > + uint8_t artificial_head; > + uint8_t compressed; > + uint8_t dynamic_pty; > + uint8_t text_ab; > + uint32_t flag_status; > +}; > + > +struct ct_info { > + uint16_t month; > + uint16_t day; > + uint16_t year; > + uint16_t hour; > + uint16_t minute; > + uint8_t local_time_offset_signbit; > + uint8_t local_time_offset_half_hour; > +}; > + > +struct af_info { > + int16_t AF_NUM; > + int16_t AF[2][25]; > + uint8_t addr_cnt; > + uint8_t ismethod_a; > + uint8_t isafnum_get; > +}; > + > +struct ps_info { > + uint8_t PS[4][8]; > + uint8_t addr_cnt; > +}; > + > +struct rt_info { > + uint8_t textdata[4][64]; > + uint8_t getlength; > + uint8_t isrtdisplay; > + uint8_t textlength; > + uint8_t istypea; > + uint8_t bufcnt; > + uint16_t addr_cnt; > +}; > + > +struct rds_raw_data { > + /* indicate if the data changed or not */ > + int dirty; > + /* the data len form chip */ > + int len; > + uint8_t data[146]; > +}; > + > +struct rds_group_cnt { > + unsigned int total; > + unsigned int groupA[16]; > + unsigned int groupB[16]; > +}; > + > +enum rds_group_cnt_opcode { > + RDS_GROUP_CNT_READ = 0, > + RDS_GROUP_CNT_WRITE, > + RDS_GROUP_CNT_RESET, > + RDS_GROUP_CNT_MAX > +}; > + > +struct rds_group_cnt_req { > + int err; > + enum rds_group_cnt_opcode op; > + struct rds_group_cnt gc; > +}; > + > +struct fm_rds_data { > + struct ct_info CT; > + struct rdslag RDSFLAG; > + uint16_t PI; > + uint8_t switch_tp; > + uint8_t PTY; > + struct af_info af_data; > + struct af_info afon_data; > + uint8_t radio_page_code; > + uint16_t program_item_number_code; > + uint8_t extend_country_code; > + uint16_t language_code; > + struct ps_info ps_data; > + uint8_t ps_on[8]; > + struct rt_info rt_data; > + uint16_t event_status; > + struct rds_group_cnt gc; > +}; > + > +/* valid Rds Flag for notify */ > +enum { > + /* Program is a traffic program */ > + RDS_FLAG_IS_TP = 0x0001, > + /* Program currently broadcasts a traffic ann. */ > + RDS_FLAG_IS_TA = 0x0002, > + /* Program currently broadcasts music */ > + RDS_FLAG_IS_MUSIC = 0x0004, > + /* Program is transmitted in stereo */ > + RDS_FLAG_IS_STEREO = 0x0008, > + /* Program is an artificial head recording */ > + RDS_FLAG_IS_ARTIFICIAL_HEAD = 0x0010, > + /* Program content is compressed */ > + RDS_FLAG_IS_COMPRESSED = 0x0020, > + /* Program type can change */ > + RDS_FLAG_IS_DYNAMIC_PTY = 0x0040, > + /* If this flag changes state, a new radio text string begins */ > + RDS_FLAG_TEXT_AB = 0x0080 > +}; > + > +enum { > + /* One of the RDS flags has changed state */ > + RDS_EVENT_FLAGS = 0x0001, > + /* The program identification code has changed */ > + RDS_EVENT_PI_CODE = 0x0002, > + /* The program type code has changed */ > + RDS_EVENT_PTY_CODE = 0x0004, > + /* The program name has changed */ > + RDS_EVENT_PROGRAMNAME = 0x0008, > + /* A new UTC date/time is available */ > + RDS_EVENT_UTCDATETIME = 0x0010, > + /* A new local date/time is available */ > + RDS_EVENT_LOCDATETIME = 0x0020, > + /* A radio text string was completed */ > + RDS_EVENT_LAST_RADIOTEXT = 0x0040, > + /* Current Channel RF signal strength too weak, need do AF switch */ > + RDS_EVENT_AF = 0x0080, > + /* An alternative frequency list is ready */ > + RDS_EVENT_AF_LIST = 0x0100, > + /* An alternative frequency list is ready */ > + RDS_EVENT_AFON_LIST = 0x0200, > + /* Other Network traffic announcement start */ > + RDS_EVENT_TAON = 0x0400, > + /* Other Network traffic announcement finished. */ > + RDS_EVENT_TAON_OFF = 0x0800, > + /* RDS Interrupt had arrived durint timer period */ > + RDS_EVENT_RDS = 0x2000, > + /* RDS Interrupt not arrived durint timer period */ > + RDS_EVENT_NO_RDS = 0x4000, > + /* Timer for RDS Bler Check. ---- BLER block error rate */ > + RDS_EVENT_RDS_TIMER = 0x8000 > +}; > + > +enum { > + FM_I2S_ON = 0, > + FM_I2S_OFF, > + FM_I2S_STATE_ERR > +}; > + > +enum { > + FM_I2S_MASTER = 0, > + FM_I2S_SLAVE, > + FM_I2S_MODE_ERR > +}; > + > +enum { > + FM_I2S_32K = 0, > + FM_I2S_44K, > + FM_I2S_48K, > + FM_I2S_SR_ERR > +}; > + > +struct fm_i2s_setting { > + int onoff; > + int mode; > + int sample; > +}; > + > +enum { > + FM_RX = 0, > + FM_TX = 1 > +}; > + > +struct fm_i2s_info_t { > + /* 0:FM_I2S_ON, 1:FM_I2S_OFF,2:error */ > + int status; > + /* 0:FM_I2S_MASTER, 1:FM_I2S_SLAVE,2:error */ > + int mode; > + /* 0:FM_I2S_32K:32000, 1:FM_I2S_44K:44100,2:FM_I2S_48K:48000,3:error */ > + int rate; > +}; > + > +enum fm_audio_path_e { > + FM_AUD_ANALOG = 0, > + FM_AUD_I2S = 1, > + FM_AUD_MRGIF = 2, > + FM_AUD_ERR > +}; > + > +enum fm_i2s_pad_sel_e { > + FM_I2S_PAD_CONN = 0, > + FM_I2S_PAD_IO = 1, > + FM_I2S_PAD_ERR > +}; > + > +struct fm_audio_info_t { > + enum fm_audio_path_e aud_path; > + struct fm_i2s_info_t i2s_info; > + enum fm_i2s_pad_sel_e i2s_pad; > +}; > + > +struct fm_cqi { > + int ch; > + int rssi; > + int reserve; > +}; > + > +struct fm_cqi_req { > + uint16_t ch_num; > + int buf_size; > + char *cqi_buf; > +}; > + > +struct fm_desense_check_t { > + int freq; > + int rssi; > +}; > + > +struct fm_full_cqi_log_t { > + /* lower band, Eg, 7600 -> 76.0Mhz */ > + uint16_t lower; > + /* upper band, Eg, 10800 -> 108.0Mhz */ > + uint16_t upper; > + /* 0x1: 50KHz, 0x2: 100Khz, 0x4: 200Khz */ > + int space; > + /* repeat times */ > + int cycle; > +}; > + > +struct fm_rx_data { > + unsigned char *addr; > + unsigned int len; > + unsigned int fifo_id; > + struct list_head entry; > +}; > + > +struct fm_rds_handle { > + /* is RDS on or off */ > + unsigned char rds_flag; > + wait_queue_head_t rx_queue; > + unsigned short new_data_flag; > +}; > + > +struct fmdrv_ops { > + struct completion completed; > + unsigned int rcv_len; > + void *read_buf; > + void *tx_buf_p; > + void *com_response; > + void *seek_response; > + unsigned int tx_len; > + unsigned char write_buf[64]; > + unsigned char com_respbuf[12]; > + unsigned char seek_respbuf[12]; > + struct tasklet_struct rx_task; > + struct tasklet_struct tx_task; > + struct fm_rds_data rds_data; > + spinlock_t rw_lock; > + struct mutex mutex; > + struct list_head rx_head; > + struct completion commontask_completion; > + struct completion seektask_completion; > + struct completion *response_completion; > + struct fm_rds_handle rds_han; > +}; > + > +#define FM_IOC_MAGIC 0xf5 > +#define FM_IOCTL_POWERUP _IOWR(FM_IOC_MAGIC, 0, struct fm_tune_parm*) > +#define FM_IOCTL_POWERDOWN _IOWR(FM_IOC_MAGIC, 1, int32_t*) > +#define FM_IOCTL_TUNE _IOWR(FM_IOC_MAGIC, 2, struct fm_tune_parm*) > +#define FM_IOCTL_SEEK _IOWR(FM_IOC_MAGIC, 3, struct fm_seek_parm*) > +#define FM_IOCTL_SETVOL _IOWR(FM_IOC_MAGIC, 4, uint32_t*) > +#define FM_IOCTL_GETVOL _IOWR(FM_IOC_MAGIC, 5, uint32_t*) > +#define FM_IOCTL_MUTE _IOWR(FM_IOC_MAGIC, 6, uint32_t*) > +#define FM_IOCTL_GETRSSI _IOWR(FM_IOC_MAGIC, 7, int32_t*) > +#define FM_IOCTL_SCAN _IOWR(FM_IOC_MAGIC, 8, struct fm_scan_parm*) > +#define FM_IOCTL_STOP_SCAN _IO(FM_IOC_MAGIC, 9) > + > +#define FM_IOCTL_GETCHIPID _IOWR(FM_IOC_MAGIC, 10, uint16_t*) > +#define FM_IOCTL_EM_TEST _IOWR(FM_IOC_MAGIC, 11, struct fm_em_parm*) > + > +#define FM_IOCTL_GETMONOSTERO _IOWR(FM_IOC_MAGIC, 13, uint16_t*) > +#define FM_IOCTL_GETCURPAMD _IOWR(FM_IOC_MAGIC, 14, uint16_t*) > +#define FM_IOCTL_GETGOODBCNT _IOWR(FM_IOC_MAGIC, 15, uint16_t*) > +#define FM_IOCTL_GETBADBNT _IOWR(FM_IOC_MAGIC, 16, uint16_t*) > +#define FM_IOCTL_GETBLERRATIO _IOWR(FM_IOC_MAGIC, 17, uint16_t*) > + > +#define FM_IOCTL_RDS_ONOFF _IOWR(FM_IOC_MAGIC, 18, uint16_t*) > +#define FM_IOCTL_RDS_SUPPORT _IOWR(FM_IOC_MAGIC, 19, int32_t*) > + > +#define FM_IOCTL_RDS_SIM_DATA _IOWR(FM_IOC_MAGIC, 23, uint32_t*) > +#define FM_IOCTL_IS_FM_POWERED_UP _IOWR(FM_IOC_MAGIC, 24, uint32_t*) > + > +#define FM_IOCTL_OVER_BT_ENABLE _IOWR(FM_IOC_MAGIC, 29, int32_t*) > + > +#define FM_IOCTL_ANA_SWITCH _IOWR(FM_IOC_MAGIC, 30, int32_t*) > +#define FM_IOCTL_GETCAPARRAY _IOWR(FM_IOC_MAGIC, 31, int32_t*) > + > +#define FM_IOCTL_I2S_SETTING _IOWR(FM_IOC_MAGIC, 33, struct fm_i2s_setting*) > + > +#define FM_IOCTL_RDS_GROUPCNT _IOWR(FM_IOC_MAGIC, 34, \ > + struct rds_group_cnt_req*) > +#define FM_IOCTL_RDS_GET_LOG _IOWR(FM_IOC_MAGIC, 35, struct rds_raw_data*) > + > +#define FM_IOCTL_SCAN_GETRSSI _IOWR(FM_IOC_MAGIC, 36, struct fm_rssi_req*) > +#define FM_IOCTL_SETMONOSTERO _IOWR(FM_IOC_MAGIC, 37, int32_t) > +#define FM_IOCTL_RDS_BC_RST _IOWR(FM_IOC_MAGIC, 38, int32_t*) > +#define FM_IOCTL_CQI_GET _IOWR(FM_IOC_MAGIC, 39, struct fm_cqi_req*) > +#define FM_IOCTL_GET_HW_INFO _IOWR(FM_IOC_MAGIC, 40, struct fm_hw_info*) > +#define FM_IOCTL_GET_I2S_INFO _IOWR(FM_IOC_MAGIC, 41, struct fm_i2s_info_t*) > +#define FM_IOCTL_IS_DESE_CHAN _IOWR(FM_IOC_MAGIC, 42, int32_t*) > +#define FM_IOCTL_TOP_RDWR _IOWR(FM_IOC_MAGIC, 43, struct fm_top_rw_parm*) > +#define FM_IOCTL_HOST_RDWR _IOWR(FM_IOC_MAGIC, 44, struct fm_host_rw_parm*) > + > +#define FM_IOCTL_PRE_SEARCH _IOWR(FM_IOC_MAGIC, 45, int32_t) > +#define FM_IOCTL_RESTORE_SEARCH _IOWR(FM_IOC_MAGIC, 46, int32_t) > + > +#define FM_IOCTL_SET_SEARCH_THRESHOLD _IOWR(FM_IOC_MAGIC, 47, \ > + fm_search_threshold_t*) > + > +#define FM_IOCTL_GET_AUDIO_INFO _IOWR(FM_IOC_MAGIC, 48, struct fm_audio_info_t*) > + > +#define FM_IOCTL_SCAN_NEW _IOWR(FM_IOC_MAGIC, 60, struct fm_scan_t*) > +#define FM_IOCTL_SEEK_NEW _IOWR(FM_IOC_MAGIC, 61, struct fm_seek_t*) > +#define FM_IOCTL_TUNE_NEW _IOWR(FM_IOC_MAGIC, 62, struct fm_tune_t*) > + > +#define FM_IOCTL_SOFT_MUTE_TUNE _IOWR(FM_IOC_MAGIC, 63, \ > + struct fm_softmute_tune_t*) > +#define FM_IOCTL_DESENSE_CHECK _IOWR(FM_IOC_MAGIC, 64, \ > + struct fm_desense_check_t*) > + > + > +/*IOCTL for SPRD SPECIAL */ > +/*audio mode:0:mono, 1:stereo; 2:blending*/ > +#define FM_IOCTL_SET_AUDIO_MODE _IOWR(FM_IOC_MAGIC, 0x47, int32_t*) > +#define FM_IOCTL_SET_REGION _IOWR(FM_IOC_MAGIC, 0x48, int32_t*) > +#define FM_IOCTL_SET_SCAN_STEP _IOWR(FM_IOC_MAGIC, 0x49, int32_t*) > +#define FM_IOCTL_CONFIG_DEEMPHASIS _IOWR(FM_IOC_MAGIC, 0x4A, int32_t*) > +#define FM_IOCTL_GET_AUDIO_MODE _IOWR(FM_IOC_MAGIC, 0x4B, int32_t*) > +#define FM_IOCTL_GET_CUR_BLER _IOWR(FM_IOC_MAGIC, 0x4C, int32_t*) > +#define FM_IOCTL_GET_SNR _IOWR(FM_IOC_MAGIC, 0x4D, int32_t*) > +#define FM_IOCTL_SOFTMUTE_ONOFF _IOWR(FM_IOC_MAGIC, 0x4E, int32_t*) > +/*Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI*/ > +#define FM_IOCTL_SET_SEEK_CRITERIA _IOWR(FM_IOC_MAGIC, 0x4F, \ > + struct fm_seek_criteria_parm*) > +/*softmute ,blending ,snr_th*/ > +#define FM_IOCTL_SET_AUDIO_THRESHOLD _IOWR(FM_IOC_MAGIC, 0x50, \ > + struct fm_audio_threshold_parm*) > +/*Frequency offset, PDP_TH,PHP_TH, SNR_TH,RSS_THI*/ > +#define FM_IOCTL_GET_SEEK_CRITERIA _IOWR(FM_IOC_MAGIC, 0x51, \ > + struct fm_seek_criteria_parm*) > +/*softmute ,blending ,snr_th*/ > +#define FM_IOCTL_GET_AUDIO_THRESHOLD _IOWR(FM_IOC_MAGIC, 0x52, \ > + struct fm_audio_threshold_parm*) > +#define FM_IOCTL_RW_REG _IOWR(FM_IOC_MAGIC, 0xC, struct fm_reg_ctl_parm*) > +#define FM_IOCTL_AF_ONOFF _IOWR(FM_IOC_MAGIC, 0x53, uint16_t*) > + > +/* IOCTL for EM */ > +#define FM_IOCTL_FULL_CQI_LOG _IOWR(FM_IOC_MAGIC, 70, \ > + struct fm_full_cqi_log_t *) > + > +#define FM_IOCTL_DUMP_REG _IO(FM_IOC_MAGIC, 0xFF) > + > +#define MAX_FM_FREQ 1080 > +#define MIN_FM_FREQ 875 > + > +#define FM_CTL_STI_MODE_NORMAL 0x0 > +#define FM_CTL_STI_MODE_SEEK 0x1 > +#define FM_CTL_STI_MODE_TUNE 0x2 > + > +#endif /* _FM_DRV_H */ > diff --git a/drivers/misc/sprd-wcn/radio/fmdrv_main.c b/drivers/misc/sprd-wcn/radio/fmdrv_main.c > new file mode 100644 > index 0000000..c48b534 > --- /dev/null > +++ b/drivers/misc/sprd-wcn/radio/fmdrv_main.c > @@ -0,0 +1,1245 @@ > +/* > + * SPREADTRUM SC2342 FM Radio driver > + * > + * Copyright (C) 2015~2017 Spreadtrum, Inc. > + * > + * SPDX-License-Identifier: GPL-2.0 > + */ > + > +#include <linux/completion.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/errno.h> > +#include <linux/fs.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/ioctl.h> > +#include <linux/miscdevice.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/sysfs.h> > +#include <linux/slab.h> > +#include <linux/timer.h> > +#include <linux/types.h> > +#include <linux/uaccess.h> > +#include <linux/wait.h> > + > +#include "fmdrv.h" > +#include "fmdrv_main.h" > +#include "fmdrv_ops.h" > +#include "fmdrv_rds_parser.h" > + > +#ifdef CONFIG_OF > +#include <linux/device.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/of_irq.h> > +#include <linux/platform_device.h> > +#endif > + > +#define FM_CHANNEL_WRITE 5 > +#define FM_CHANNEL_READ 10 > +#define FM_WRITE_SIZE (64) > +#define FM_READ_SIZE (128) > +#define FM_TYPE 1 > +#define FM_SUBTYPE0 0 > +#define FM_SUBTYPE1 1 > +#define FM_SUBTYPE2 2 > +#define FM_SUBTYPE3 3 > + > +#define HCI_GRP_VENDOR_SPECIFIC 0x3F > +#define FM_SPRD_OP_CODE 0x008C > +#define hci_opcode_pack(ogf, ocf) \ > + ((unsigned short) ((ocf & 0x03ff) | (ogf << 10))) > +#define HCI_EV_CMD_COMPLETE 0x0e > +#define HCI_VS_EVENT 0xFF > + > +#define SEEKFORMAT "rssi_th =%d,snr_th =%d,freq_offset_th =%d," \ > + "pilot_power_th= %d,noise_power_th=%d" > +#define AUDIOFORMAT "hbound=%d,lbound =%d,power_th =%d," \ > + "phyt= %d,snr_th=%d" > +bool read_flag; > +struct fmdrv_ops *fmdev; > +static struct fm_rds_data *g_rds_data_string; > + > +/* for driver test */ > +#define RX_NUM 100 > +static unsigned char *buf_addr; > +static char a[RX_NUM] = {1, 2, 3, 4, 5}; > +static unsigned char r1[11] = {0x04, 0x0e, 0x08, 0x01, 0x8c, 0xfc, > + 0x00, 0xa1, 0x23, 0x12, 0x2A}; > +static unsigned char r2[9] = {0x04, 0xFF, 0x6, 0x30, 0x00, 0x12, 0x13, > + 0xb4, 0x23}; > +static unsigned int (*rx_cb)(void *addr, unsigned int len, > + unsigned int fifo_id); > +static unsigned int (*tx_cb)(void *addr); > +static struct timer_list test_timer; > + > +static void sdiom_register_pt_rx_process(unsigned int type, > + unsigned int subtype, > + void *func) > +{ > + rx_cb = func; > +} > + > +static void sdiom_register_pt_tx_release(unsigned int type, > + unsigned int subtype, > + void *func) > +{ > + tx_cb = func; > +} > + > +static unsigned int sdiom_pt_write(void *buf, unsigned int len, > + int type, int subtype) > +{ > + int i = 0; > + > + buf_addr = buf; > + pr_info("fmdrv sdiom_pt_write len is %d\n", len); > + for (i = 0; i < len; i++) > + pr_info("fmdrv send data is %x\n", *(buf_addr+i)); > + > + mod_timer(&test_timer, jiffies + msecs_to_jiffies(30)); > + > + return 0; > +} > + > +unsigned int sdiom_pt_read_release(unsigned int fifo_id) > +{ > + return 0; > +} > + > +int start_marlin(int type) > +{ > + return 0; > +} > + > +int stop_marlin(int type) > +{ > + return 0; > +} > + > +static void timer_cb(unsigned long data) > +{ > + rx_cb(r1, 11, 0); > + if (*(buf_addr+4) == 0x04) { > + mdelay(100); > + rx_cb(r2, 9, 0); > + } > +} > + > +static void test_init(void) > +{ > + int i; > + > + for (i = 0; i < RX_NUM; i++) > + a[i] = i; > +} > + > +static int fm_send_cmd(unsigned char subcmd, void *payload, > + int payload_len) > +{ > + unsigned char *cmd_buf; > + struct fm_cmd_hdr *cmd_hdr; > + int size; > + int ret = 0; > + > + size = sizeof(struct fm_cmd_hdr) + > + ((payload == NULL) ? 0 : payload_len); > + > + cmd_buf = kmalloc(size, GFP_KERNEL); > + if (!cmd_buf) > + return -ENOMEM; > + > + /* Fill command information */ > + cmd_hdr = (struct fm_cmd_hdr *)cmd_buf; > + cmd_hdr->header = 0x01; > + cmd_hdr->opcode = hci_opcode_pack(HCI_GRP_VENDOR_SPECIFIC, > + FM_SPRD_OP_CODE); > + cmd_hdr->len = ((payload == NULL) ? 0 : payload_len) + 1; > + cmd_hdr->fm_subcmd = subcmd; > + > + if (payload != NULL) > + memcpy(cmd_buf + sizeof(struct fm_cmd_hdr), > + payload, payload_len); > + fmdev->tx_buf_p = cmd_buf; > + fmdev->tx_len = size; > + > + ret = sdiom_pt_write(fmdev->tx_buf_p, size, FM_TYPE, FM_SUBTYPE0); > + if (ret != 0) { > + pr_err("fmdrv write cmd to sdiom fail Error number=%d\n", ret); > + return -EBUSY; > + } > + > + return 0; > +} > + > +static int fm_write_cmd(unsigned char subcmd, void *payload, > + unsigned char payload_len, void *response, > + unsigned char *response_len) > +{ > + unsigned long timeleft; > + int ret; > + > + mutex_lock(&fmdev->mutex); > + init_completion(&fmdev->commontask_completion); > + ret = fm_send_cmd(subcmd, payload, payload_len); > + if (ret < 0) { > + mutex_unlock(&fmdev->mutex); > + return ret; > + } > + > + timeleft = wait_for_completion_timeout(&fmdev->commontask_completion, > + FM_DRV_TX_TIMEOUT); > + if (!timeleft) { > + pr_err("(fmdrv) %s(): Timeout(%d sec),didn't get fm SubCmd\n" > + "0x%02X completion signal from RX tasklet\n", > + __func__, jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000, subcmd); > + mutex_unlock(&fmdev->mutex); > + return -ETIMEDOUT; > + } > + > + mutex_unlock(&fmdev->mutex); > + pr_debug("fmdrv wait command have complete\n"); > + /* 0:len; XX XX XX sttaus */ > + if ((fmdev->com_respbuf[4]) != 0) { > + pr_err("(fmdrv) %s(): Response status not success for 0x%02X\n", > + __func__, subcmd); > + return -EFAULT; > + } > + pr_info("(fmdrv) %s(): Response status success for 0x%02X: %d\n", > + __func__, subcmd, fmdev->com_respbuf[4]); > + /* the event : 04 0e len 01 8C fc 00(status) rssi snr freq .p->len */ > + if (response != NULL && response_len != NULL) > + memcpy(response, &(fmdev->com_respbuf[5]), > + fmdev->com_respbuf[0]-4); > + > + return 0; > +} > + > +static void receive_tasklet(unsigned long arg) > +{ > + struct fmdrv_ops *fmdev; > + struct fm_rx_data *rx = NULL; > + /* the data from SDIO is event data */ > + unsigned char *pdata; > + > + fmdev = (struct fmdrv_ops *)arg; > + if (unlikely(!fmdev)) { > + pr_err("fm_rx_task fmdev is NULL\n"); > + return; > + } > + pr_info("fm %s start running\n", __func__); > + while (!list_empty(&fmdev->rx_head)) { > + spin_lock_bh(&fmdev->rw_lock); > + > + rx = list_first_entry_or_null(&fmdev->rx_head, > + struct fm_rx_data, entry); > + if (rx) > + list_del(&rx->entry); > + > + else { > + spin_unlock_bh(&fmdev->rw_lock); > + return; > + } > + pdata = rx->addr; > + > + if ((*((rx->addr)+1)) == 0x0e) { > + memcpy(fmdev->com_respbuf, pdata + 2, (*(pdata+2)) + 1); > + pr_debug("fm RX before commontask_completion=0x%x\n", > + fmdev->commontask_completion.done); > + complete(&fmdev->commontask_completion); > + pr_debug("fm RX after commontask_completion=0x%x\n", > + fmdev->commontask_completion.done); > + sdiom_pt_read_release(rx->fifo_id); > + pr_info("fmdrv release fifo_id is %d\n", rx->fifo_id); > + } > + > + else if (((*((rx->addr)+1)) == 0xFF) && > + ((*((rx->addr)+3)) == 0x30)) { > + memcpy(fmdev->seek_respbuf, pdata + 2, > + (*(pdata+2)) + 1); > + /*fmdev->seek_response = rx;*/ > + pr_debug("fm RX before seektask_completion=0x%x\n", > + fmdev->seektask_completion.done); > + complete(&fmdev->seektask_completion); > + pr_debug("fm RX after seektask_completion=0x%x\n", > + fmdev->seektask_completion.done); > + sdiom_pt_read_release(rx->fifo_id); > + pr_info("fmdrv release fifo_id is %d\n", rx->fifo_id); > + } > + > + else if (((*((rx->addr)+1)) == 0xFF) && > + ((*((rx->addr)+3)) == 0x00)) > + rds_parser(pdata + 4, 12, rx->fifo_id); > + else { > + pr_err("fmdrv error:unknown event !!!\n"); > + sdiom_pt_read_release(rx->fifo_id); > + pr_info("fmdrv release fifo_id is %d\n", rx->fifo_id); > + } > + > + kfree(rx); > + rx = NULL; > + spin_unlock_bh(&fmdev->rw_lock); > + } > +} > + > +ssize_t fm_read_rds_data(struct file *filp, char __user *buf, > + size_t count, loff_t *pos) > +{ > + int timeout = -1; > + int ret; > + > + pr_info("(FM_RDS) fm start to read RDS data\n"); > + > + if (filp->f_flags & O_NONBLOCK) { > + timeout = 0; > + pr_err("fm_read_rds_data NON BLOCK!!!\n"); > + return -EWOULDBLOCK; > + } > + > + if (timeout < 0) { > + /* wait forever */ > + ret = wait_event_interruptible((fmdev->rds_han.rx_queue), > + ((fmdev->rds_han.new_data_flag) == 1)); > + if (ret) { > + pr_err("(FM RDS)wait_event_interruptible ret=%d\n", > + ret); > + return -EINTR; > + } > + } > + > + fmdev->rds_data.rt_data.textlength = > + strlen(fmdev->rds_data.rt_data.textdata[3]); > + pr_info("fm RT len is %d\n", fmdev->rds_data.rt_data.textlength); > + if (copy_to_user(buf, &(fmdev->rds_data), sizeof(fmdev->rds_data))) { > + pr_info("fm_read_rds_data ret value is -eFAULT\n"); > + return -EFAULT; > + } > + pr_info("(fm drs) fm event is %x\n", fmdev->rds_data.event_status); > + fmdev->rds_data.event_status = 0; > + > + pr_info("fmevent_status=%x\n", fmdev->rds_data.event_status); > + pr_info("PS=%s\n", fmdev->rds_data.ps_data.PS[3]); > + pr_info("fm_read_rds_data end....\n"); > + > + return sizeof(fmdev->rds_data); > +} > + > +void parse_at_fm_cmd(unsigned int *freq_found) > +{ > + int comma_cou = 0; > + int i = 0; > + int cmdstart = 0; > + int len = 0; > + char *cur_ptr; > + char num_str[6] = {0}; > + int result = 0; > + > + cur_ptr = fmdev->read_buf; > + read_flag = 0; > + for (i = 0; i < 32 && cur_ptr[i] != '\0'; i++) { > + if (cur_ptr[i] == ',') > + comma_cou++; > + if (comma_cou == 3) { > + comma_cou = 0; > + cmdstart = i; > + } > + } > + for (i = 0, cmdstart++; cmdstart < 32 && cur_ptr[cmdstart] != '\0' > + && cur_ptr[cmdstart] != ','; i++, cmdstart++) { > + if (cur_ptr[cmdstart] >= '0' && cur_ptr[cmdstart] <= '9') > + num_str[i] = cur_ptr[cmdstart]; > + else if (cur_ptr[cmdstart] == ' ') > + break; > + } > + len = strlen(num_str); > + cur_ptr = num_str; > + result = cur_ptr[0] - '0'; > + for (i = 1; i < len; i++) > + result = result * 10 + cur_ptr[i] - '0'; > + *freq_found = result; > + pr_info("fm seek event have come freq=%d\n", result); > +} > + > +int fm_open(struct inode *inode, struct file *filep) > +{ > + pr_info("start open SPRD fm module...\n"); > + > + return 0; > +} > + > +void fm_sdio_read(void) > +{ > + memset(fmdev->read_buf, 0, FM_READ_SIZE); > + if (fmdev->rcv_len <= 0) { > + pr_err("FM_CHANNEL_READ len err\n"); > + return; > + } > + if (fmdev->rcv_len > FM_READ_SIZE) > + pr_err("The read data len:%d, beyond max read:%d", > + fmdev->rcv_len, FM_READ_SIZE); > + pr_info("* fmdev->read_buf: %s *\n", (char *)fmdev->read_buf); > +} > + > +int fm_sdio_write(unsigned char *buffer, unsigned int size) > +{ > + printk_ratelimited("%s size: %d\n", __func__, size); > + > + return size; > +} > + > +int fm_sdio_init(void) > +{ > + return 0; > +} > + > +unsigned int fm_rx_cback(void *addr, unsigned int len, unsigned int fifo_id) > +{ > + unsigned char *buf; > + > + buf = (unsigned char *)addr; > + > + if (fmdev != NULL) { > + struct fm_rx_data *rx = > + kmalloc(sizeof(struct fm_rx_data), GFP_KERNEL); > + if (!rx) { > + pr_err("(fmdrv): %s(): No memory to create fm rx buf\n", > + __func__); > + sdiom_pt_read_release(fifo_id); > + return -ENOMEM; > + } > + rx->addr = (unsigned char *)addr; > + rx->len = len; > + rx->fifo_id = fifo_id; > + spin_lock_bh(&fmdev->rw_lock); > + list_add_tail(&rx->entry, &fmdev->rx_head); > + spin_unlock_bh(&fmdev->rw_lock); > + pr_debug("(fmdrv) %s(): tasklet_schedule start\n", __func__); > + tasklet_schedule(&fmdev->rx_task); > + } > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(fm_rx_cback); > + > +void fm_tx_cback(void *tx_buff) > +{ > + if (tx_buff != NULL) > + kfree(tx_buff); > +} > +EXPORT_SYMBOL_GPL(fm_tx_cback); > + > +int fm_write(unsigned char *array, unsigned char len) > +{ > + unsigned long timeleft; > + int cnt = 0; > + > + cnt = 0; > + /* len = strlen(array); */ > + fm_sdio_write(array, len); > + > + timeleft = wait_for_completion_timeout(&fmdev->completed, > + FM_DRV_TX_TIMEOUT); > + if (!timeleft) { > + pr_err("Timeout, %d\n", ETIMEDOUT); > + return -ETIMEDOUT; > + > + } > + > + pr_debug("success!\n"); > + > + return 0; > +} > + > +int fm_powerup(void *arg) > +{ > + struct fm_tune_parm parm; > + unsigned short payload; > + int ret = -1; > + > + if (copy_from_user(&parm, arg, sizeof(parm))) { > + pr_err("fm powerup 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + > + if (start_marlin(MARLIN_FM)) { > + pr_err("marlin2 chip %s failed\n", __func__); > + return -ENODEV; > + } > + > + parm.freq *= 10; > + pr_info("fm ioctl power up freq= %d\n", parm.freq); > + payload = parm.freq; > + ret = fm_write_cmd(FM_POWERUP_CMD, &payload, > + sizeof(payload), NULL, NULL); > + if (ret < 0) > + pr_err("(fmdrv) %s FM write pwrup cmd status failed %d\n", > + __func__, ret); > + > + return ret; > +} > + > +int fm_powerdown(void) > +{ > + int ret = -EINVAL; > + unsigned char payload = FM_OFF; > + > + fmdev->rds_han.new_data_flag = 1; > + wake_up_interruptible(&fmdev->rds_han.rx_queue); > + ret = fm_write_cmd(FM_POWERDOWN_CMD, &payload, sizeof(payload), > + NULL, NULL); > + if (ret < 0) > + pr_err("(fmdrv) %s FM write pwrdown cmd status failed %d\n", > + __func__, ret); > + > + return ret; > +} > + > +int fm_tune(void *arg) > +{ struct fm_tune_parm parm; > + int ret = 0; > + unsigned char respond_buf[4], respond_len; > + unsigned short freq; > + > + if (copy_from_user(&parm, arg, sizeof(parm))) { > + pr_info("fm tune 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + parm.freq *= 10; > + pr_debug("fm ioctl tune freq = %d\n", parm.freq); > + ret = fm_write_cmd(FM_TUNE_CMD, &parm.freq, sizeof(parm.freq), > + respond_buf, &respond_len); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write tune cmd status failed %d\n", > + __func__, ret); > + return ret; > + } > + freq = respond_buf[2] + (respond_buf[3] << 8); > + pr_debug("(fmdrv) fm tune have finshed!!status =0,RSSI=%d\n" > + "(fmdrv) SNR=%d,freq=%d\n", respond_buf[0], respond_buf[1], > + freq); > + > + return ret; > +} > + > +/* > + * seek cmd :01 8C FC 04(length) 04 freq(16bit) seekdir(8bit) > + * payload == freq,seekdir > + * seek event:status,RSSI,SNR,Freq > + */ > +int fm_seek(void *arg) > +{ struct fm_seek_parm parm; > + int ret = 0; > + unsigned char payload[3]; > + unsigned char respond_buf[5]; > + unsigned long timeleft; > + > + if (copy_from_user(&parm, arg, sizeof(parm))) { > + pr_info("fm seek 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + parm.freq *= 10; > + payload[0] = (parm.freq & 0xFF); > + payload[1] = (parm.freq >> 8); > + payload[2] = parm.seekdir; > + pr_info("fm ioctl seek freq=%d,dir =%d\n", parm.freq, parm.seekdir); > + ret = fm_write_cmd(FM_SEEK_CMD, payload, sizeof(payload), NULL, NULL); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write seek cmd status failed %d\n", > + __func__, ret); > + return ret; > + } > + init_completion(&fmdev->seektask_completion); > + timeleft = wait_for_completion_timeout(&fmdev->seektask_completion, > + FM_DRV_RX_SEEK_TIMEOUT); > + if (!timeleft) { > + pr_err("(fmdrv) %s(): Timeout(%d sec),didn't get fm seek end !\n", > + __func__, jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000); > + /* -110 */ > + return -ETIMEDOUT; > + } > + > + memcpy(respond_buf, &(fmdev->seek_respbuf[2]), > + fmdev->seek_respbuf[0] - 1); > + > + parm.freq = respond_buf[3] + (respond_buf[4] << 8); > + parm.freq /= 10; > + pr_info("(fmdrv) fm seek have finshed!!status = %d, RSSI=%d\n" > + "(fmdrv) fm seek SNR=%d, freq=%d\n", respond_buf[0], > + respond_buf[1], respond_buf[2], parm.freq); > + /* pass the value to user space */ > + if (copy_to_user(arg, &parm, sizeof(parm))) > + ret = -EFAULT; > + > + return ret; > +} > + > +/* > + * mute cmd :01 8C FC 02(length) 02 mute(8bit) > + * mute event:status,ismute > + */ > +int fm_mute(void *arg) > +{ > + unsigned char mute = 0; > + int ret = -1; > + > + if (copy_from_user(&mute, arg, sizeof(mute))) { > + pr_err("fm mute 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + > + if (mute == 1) > + pr_info("fm ioctl mute\n"); > + else if (mute == 0) > + pr_info("fm ioctl unmute\n"); > + else > + pr_info("fm ioctl unknown cmd mute\n"); > + > + ret = fm_write_cmd(FM_MUTE_CMD, &mute, > + sizeof(mute), NULL, NULL); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write mute cmd status failed %d\n", > + __func__, ret); > + return ret; > + } > + > + return ret; > +} > + > +int fm_set_volume(void *arg) > +{ > + unsigned char vol; > + int ret = 0; > + > + if (copy_from_user(&vol, arg, sizeof(vol))) { > + pr_err("fm set volume 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + pr_info("fm ioctl set_volume =%d\n", vol); > + ret = fm_write_cmd(FM_SET_VOLUME_CMD, &vol, sizeof(vol), > + NULL, NULL); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM set volume status failed %d\n", > + __func__, ret); > + return ret; > + } > + > + return ret; > +} > + > +int fm_get_volume(void *arg) > +{ > + unsigned char payload = 0; > + unsigned char res_len; > + int volume; > + unsigned char resp_buf[1]; > + int ret = -1; > + > + pr_info("fm ioctl get volume =0x%x\n", volume); > + ret = fm_write_cmd(FM_GET_VOLUME_CMD, &payload, sizeof(payload), > + &resp_buf[0], &res_len); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write get volime cmd status failed %d\n", > + __func__, ret); > + return ret; > + } > + > + volume = (int)resp_buf[0]; > + if (copy_to_user(arg, &volume, sizeof(volume))) > + ret = -EFAULT; > + > + return ret; > + > +} > + > +int fm_stop_scan(void *arg) > +{ > + int ret = -EINVAL; > + > + pr_info("fm ioctl stop scan\n"); > + ret = fm_write_cmd(FM_SEARCH_ABORT, NULL, 0, > + NULL, NULL); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write stop scan cmd status failed %d\n", > + __func__, ret); > + > + return ret; > + } > + > + return ret; > +} > + > +int fm_scan_all(void *arg) > +{ > + struct fm_scan_all_parm parm; > + int ret = 0; > + unsigned char respond_len; > + struct fm_scan_all_parm respond_buf; > + > + > + pr_info("fm ioctl scan all\n"); > + if (copy_from_user(&parm, arg, sizeof(parm))) { > + pr_err("fm search all 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + > + ret = fm_write_cmd(FM_SCAN_ALL_CMD, &parm, sizeof(parm), > + &respond_buf, &respond_len); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write scan all cmd status failed %d\n", > + __func__, ret); > + return ret; > + } > + if (copy_to_user(arg, &parm, sizeof(parm))) > + ret = -EFAULT; > + > + return ret; > +} > + > +int fm_rw_reg(void *arg) > +{ > + struct fm_reg_ctl_parm parm; > + int ret = 0; > + unsigned char respond_len; > + > + if (copy_from_user(&parm, arg, sizeof(parm))) { > + pr_err("fm read and write register 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + pr_info("fm ioctl read write reg = %d\n", parm.rw_flag); > + ret = fm_write_cmd(FM_READ_WRITE_REG_CMD, &parm, sizeof(parm), > + &parm, &respond_len); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM write register cmd status failed %d\n", > + __func__, ret); > + return ret; > + } > + if (copy_to_user(arg, &parm, sizeof(parm))) > + ret = -EFAULT; > + > + return ret; > +} > + > +int fm_get_monostero(void *arg) > +{ > + return 0; > +} > + > +/* audio mode: 0:None 1: mono 2:steron */ > +int fm_set_audio_mode(void *arg) > +{ > + unsigned char mode; > + int ret = 0; > + > + if (copy_from_user(&mode, arg, sizeof(mode))) { > + pr_err("fm set audio mode 's ret value is -eFAULT\n"); > + return -EFAULT; > + } > + pr_info("fm ioctl set audio mode =%d\n", mode); > + ret = fm_write_cmd(FM_SET_AUDIO_MODE, &mode, sizeof(mode), > + NULL, NULL); > + if (ret < 0) { > + pr_err("(fmdrv) %s FM set audio mode status failed %d\n", > + __func__, ret); > + return ret; > + } > + > + return ret; > +} > + > +int fm_set_region(void *arg) > +{ > + unsigned char region; > + int ret = 0; > + > + if (copy_from_user(®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