// SPDX-License-Identifier: GPL-2.0-only /* * drivers/mfd/si476x-cmd.c -- Subroutines implementing command * protocol of si476x series of chips * * Copyright (C) 2012 Innovative Converged Devices(ICD) * Copyright (C) 2013 Andrey Smirnov * * Author: Andrey Smirnov <[email protected]> */ #include <linux/module.h> #include <linux/completion.h> #include <linux/delay.h> #include <linux/atomic.h> #include <linux/i2c.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/videodev2.h> #include <linux/mfd/si476x-core.h> #include <linux/unaligned.h> #define msb(x) … #define lsb(x) … #define CMD_POWER_UP … #define CMD_POWER_UP_A10_NRESP … #define CMD_POWER_UP_A10_NARGS … #define CMD_POWER_UP_A20_NRESP … #define CMD_POWER_UP_A20_NARGS … #define POWER_UP_DELAY_MS … #define CMD_POWER_DOWN … #define CMD_POWER_DOWN_A10_NRESP … #define CMD_POWER_DOWN_A20_NRESP … #define CMD_POWER_DOWN_A20_NARGS … #define CMD_FUNC_INFO … #define CMD_FUNC_INFO_NRESP … #define CMD_SET_PROPERTY … #define CMD_SET_PROPERTY_NARGS … #define CMD_SET_PROPERTY_NRESP … #define CMD_GET_PROPERTY … #define CMD_GET_PROPERTY_NARGS … #define CMD_GET_PROPERTY_NRESP … #define CMD_AGC_STATUS … #define CMD_AGC_STATUS_NRESP_A10 … #define CMD_AGC_STATUS_NRESP_A20 … #define PIN_CFG_BYTE(x) … #define CMD_DIG_AUDIO_PIN_CFG … #define CMD_DIG_AUDIO_PIN_CFG_NARGS … #define CMD_DIG_AUDIO_PIN_CFG_NRESP … #define CMD_ZIF_PIN_CFG … #define CMD_ZIF_PIN_CFG_NARGS … #define CMD_ZIF_PIN_CFG_NRESP … #define CMD_IC_LINK_GPO_CTL_PIN_CFG … #define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS … #define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP … #define CMD_ANA_AUDIO_PIN_CFG … #define CMD_ANA_AUDIO_PIN_CFG_NARGS … #define CMD_ANA_AUDIO_PIN_CFG_NRESP … #define CMD_INTB_PIN_CFG … #define CMD_INTB_PIN_CFG_NARGS … #define CMD_INTB_PIN_CFG_A10_NRESP … #define CMD_INTB_PIN_CFG_A20_NRESP … #define CMD_FM_TUNE_FREQ … #define CMD_FM_TUNE_FREQ_A10_NARGS … #define CMD_FM_TUNE_FREQ_A20_NARGS … #define CMD_FM_TUNE_FREQ_NRESP … #define CMD_FM_RSQ_STATUS … #define CMD_FM_RSQ_STATUS_A10_NARGS … #define CMD_FM_RSQ_STATUS_A10_NRESP … #define CMD_FM_RSQ_STATUS_A30_NARGS … #define CMD_FM_RSQ_STATUS_A30_NRESP … #define CMD_FM_SEEK_START … #define CMD_FM_SEEK_START_NARGS … #define CMD_FM_SEEK_START_NRESP … #define CMD_FM_RDS_STATUS … #define CMD_FM_RDS_STATUS_NARGS … #define CMD_FM_RDS_STATUS_NRESP … #define CMD_FM_RDS_BLOCKCOUNT … #define CMD_FM_RDS_BLOCKCOUNT_NARGS … #define CMD_FM_RDS_BLOCKCOUNT_NRESP … #define CMD_FM_PHASE_DIVERSITY … #define CMD_FM_PHASE_DIVERSITY_NARGS … #define CMD_FM_PHASE_DIVERSITY_NRESP … #define CMD_FM_PHASE_DIV_STATUS … #define CMD_FM_PHASE_DIV_STATUS_NRESP … #define CMD_AM_TUNE_FREQ … #define CMD_AM_TUNE_FREQ_NARGS … #define CMD_AM_TUNE_FREQ_NRESP … #define CMD_AM_RSQ_STATUS … #define CMD_AM_RSQ_STATUS_NARGS … #define CMD_AM_RSQ_STATUS_NRESP … #define CMD_AM_SEEK_START … #define CMD_AM_SEEK_START_NARGS … #define CMD_AM_SEEK_START_NRESP … #define CMD_AM_ACF_STATUS … #define CMD_AM_ACF_STATUS_NRESP … #define CMD_AM_ACF_STATUS_NARGS … #define CMD_FM_ACF_STATUS … #define CMD_FM_ACF_STATUS_NRESP … #define CMD_FM_ACF_STATUS_NARGS … #define CMD_MAX_ARGS_COUNT … enum si476x_acf_status_report_bits { … }; enum si476x_agc_status_report_bits { … }; enum si476x_errors { … }; static int si476x_core_parse_and_nag_about_error(struct si476x_core *core) { … } /** * si476x_core_send_command() - sends a command to si476x and waits its * response * @core: si476x_device structure for the device we are * communicating with * @command: command id * @args: command arguments we are sending * @argn: actual size of @args * @resp: buffer to place the expected response from the device * @respn: actual size of @resp * @usecs: amount of time to wait before reading the response (in * usecs) * * Function returns 0 on success and negative error code on * failure */ static int si476x_core_send_command(struct si476x_core *core, const u8 command, const u8 args[], const int argn, u8 resp[], const int respn, const int usecs) { … } static int si476x_cmd_clear_stc(struct si476x_core *core) { … } static int si476x_cmd_tune_seek_freq(struct si476x_core *core, uint8_t cmd, const uint8_t args[], size_t argn, uint8_t *resp, size_t respn) { … } /** * si476x_core_cmd_func_info() - send 'FUNC_INFO' command to the device * @core: device to send the command to * @info: struct si476x_func_info to fill all the information * returned by the command * * The command requests the firmware and patch version for currently * loaded firmware (dependent on the function of the device FM/AM/WB) * * Function returns 0 on success and negative error code on * failure */ int si476x_core_cmd_func_info(struct si476x_core *core, struct si476x_func_info *info) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_set_property() - send 'SET_PROPERTY' command to the device * @core: device to send the command to * @property: property address * @value: property value * * Function returns 0 on success and negative error code on * failure */ int si476x_core_cmd_set_property(struct si476x_core *core, u16 property, u16 value) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_get_property() - send 'GET_PROPERTY' command to the device * @core: device to send the command to * @property: property address * * Function return the value of property as u16 on success or a * negative error on failure */ int si476x_core_cmd_get_property(struct si476x_core *core, u16 property) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_dig_audio_pin_cfg() - send 'DIG_AUDIO_PIN_CFG' command to * the device * @core: device to send the command to * @dclk: DCLK pin function configuration: * #SI476X_DCLK_NOOP - do not modify the behaviour * #SI476X_DCLK_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * #SI476X_DCLK_DAUDIO - set the pin to be a part of digital * audio interface * @dfs: DFS pin function configuration: * #SI476X_DFS_NOOP - do not modify the behaviour * #SI476X_DFS_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_DFS_DAUDIO - set the pin to be a part of digital * audio interface * @dout: - DOUT pin function configuration: * SI476X_DOUT_NOOP - do not modify the behaviour * SI476X_DOUT_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_DOUT_I2S_OUTPUT - set this pin to be digital out on I2S * port 1 * SI476X_DOUT_I2S_INPUT - set this pin to be digital in on I2S * port 1 * @xout: - XOUT pin function configuration: * SI476X_XOUT_NOOP - do not modify the behaviour * SI476X_XOUT_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_XOUT_I2S_INPUT - set this pin to be digital in on I2S * port 1 * SI476X_XOUT_MODE_SELECT - set this pin to be the input that * selects the mode of the I2S audio * combiner (analog or HD) * [SI4761/63/65/67 Only] * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *core, enum si476x_dclk_config dclk, enum si476x_dfs_config dfs, enum si476x_dout_config dout, enum si476x_xout_config xout) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_zif_pin_cfg - send 'ZIF_PIN_CFG_COMMAND' * @core: - device to send the command to * @iqclk: - IQCL pin function configuration: * SI476X_IQCLK_NOOP - do not modify the behaviour * SI476X_IQCLK_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_IQCLK_IQ - set pin to be a part of I/Q interface * in master mode * @iqfs: - IQFS pin function configuration: * SI476X_IQFS_NOOP - do not modify the behaviour * SI476X_IQFS_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_IQFS_IQ - set pin to be a part of I/Q interface * in master mode * @iout: - IOUT pin function configuration: * SI476X_IOUT_NOOP - do not modify the behaviour * SI476X_IOUT_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_IOUT_OUTPUT - set pin to be I out * @qout: - QOUT pin function configuration: * SI476X_QOUT_NOOP - do not modify the behaviour * SI476X_QOUT_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_QOUT_OUTPUT - set pin to be Q out * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_zif_pin_cfg(struct si476x_core *core, enum si476x_iqclk_config iqclk, enum si476x_iqfs_config iqfs, enum si476x_iout_config iout, enum si476x_qout_config qout) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_ic_link_gpo_ctl_pin_cfg - send * 'IC_LINK_GPIO_CTL_PIN_CFG' command to the device * @core: - device to send the command to * @icin: - ICIN pin function configuration: * SI476X_ICIN_NOOP - do not modify the behaviour * SI476X_ICIN_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_ICIN_GPO1_HIGH - set pin to be an output, drive it high * SI476X_ICIN_GPO1_LOW - set pin to be an output, drive it low * SI476X_ICIN_IC_LINK - set the pin to be a part of Inter-Chip link * @icip: - ICIP pin function configuration: * SI476X_ICIP_NOOP - do not modify the behaviour * SI476X_ICIP_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_ICIP_GPO1_HIGH - set pin to be an output, drive it high * SI476X_ICIP_GPO1_LOW - set pin to be an output, drive it low * SI476X_ICIP_IC_LINK - set the pin to be a part of Inter-Chip link * @icon: - ICON pin function configuration: * SI476X_ICON_NOOP - do not modify the behaviour * SI476X_ICON_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_ICON_I2S - set the pin to be a part of audio * interface in slave mode (DCLK) * SI476X_ICON_IC_LINK - set the pin to be a part of Inter-Chip link * @icop: - ICOP pin function configuration: * SI476X_ICOP_NOOP - do not modify the behaviour * SI476X_ICOP_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_ICOP_I2S - set the pin to be a part of audio * interface in slave mode (DOUT) * [Si4761/63/65/67 Only] * SI476X_ICOP_IC_LINK - set the pin to be a part of Inter-Chip link * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *core, enum si476x_icin_config icin, enum si476x_icip_config icip, enum si476x_icon_config icon, enum si476x_icop_config icop) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_ana_audio_pin_cfg - send 'ANA_AUDIO_PIN_CFG' to the * device * @core: - device to send the command to * @lrout: - LROUT pin function configuration: * SI476X_LROUT_NOOP - do not modify the behaviour * SI476X_LROUT_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_LROUT_AUDIO - set pin to be audio output * SI476X_LROUT_MPX - set pin to be MPX output * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *core, enum si476x_lrout_config lrout) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_intb_pin_cfg_a10 - send 'INTB_PIN_CFG' command to the device * @core: - device to send the command to * @intb: - INTB pin function configuration: * SI476X_INTB_NOOP - do not modify the behaviour * SI476X_INTB_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_INTB_DAUDIO - set pin to be a part of digital * audio interface in slave mode * SI476X_INTB_IRQ - set pin to be an interrupt request line * @a1: - A1 pin function configuration: * SI476X_A1_NOOP - do not modify the behaviour * SI476X_A1_TRISTATE - put the pin in tristate condition, * enable 1MOhm pulldown * SI476X_A1_IRQ - set pin to be an interrupt request line * * Function returns 0 on success and negative error code on failure */ static int si476x_core_cmd_intb_pin_cfg_a10(struct si476x_core *core, enum si476x_intb_config intb, enum si476x_a1_config a1) { … } static int si476x_core_cmd_intb_pin_cfg_a20(struct si476x_core *core, enum si476x_intb_config intb, enum si476x_a1_config a1) { … } /** * si476x_core_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the * device * @core: - device to send the command to * @rsqargs: - pointer to a structure containing a group of sub-args * relevant to sending the RSQ status command * @report: - all signal quality information returned by the command * (if NULL then the output of the command is ignored) * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_am_rsq_status(struct si476x_core *core, struct si476x_rsq_status_args *rsqargs, struct si476x_rsq_status_report *report) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_fm_acf_status(struct si476x_core *core, struct si476x_acf_status_report *report) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_am_acf_status(struct si476x_core *core, struct si476x_acf_status_report *report) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_fm_seek_start - send 'FM_SEEK_START' command to the * device * @core: - device to send the command to * @seekup: - if set the direction of the search is 'up' * @wrap: - if set seek wraps when hitting band limit * * This function begins search for a valid station. The station is * considered valid when 'FM_VALID_SNR_THRESHOLD' and * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria * are met. } * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_fm_seek_start(struct si476x_core *core, bool seekup, bool wrap) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the * device * @core: - device to send the command to * @status_only: - if set the data is not removed from RDSFIFO, * RDSFIFOUSED is not decremented and data in all the * rest RDS data contains the last valid info received * @mtfifo: if set the command clears RDS receive FIFO * @intack: if set the command clards the RDSINT bit. * @report: - all signal quality information returned by the command * (if NULL then the output of the command is ignored) * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_fm_rds_status(struct si476x_core *core, bool status_only, bool mtfifo, bool intack, struct si476x_rds_status_report *report) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core, bool clear, struct si476x_rds_blockcount_report *report) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core, enum si476x_phase_diversity_mode mode) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_fm_phase_div_status() - get the phase diversity * status * * @core: si476x device * * NOTE caller must hold core lock * * Function returns the value of the status bit in case of success and * negative error code in case of failure. */ int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core) { … } EXPORT_SYMBOL_GPL(…); /** * si476x_core_cmd_am_seek_start - send 'FM_SEEK_START' command to the * device * @core: - device to send the command to * @seekup: - if set the direction of the search is 'up' * @wrap: - if set seek wraps when hitting band limit * * This function begins search for a valid station. The station is * considered valid when 'FM_VALID_SNR_THRESHOLD' and * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria * are met. * * Function returns 0 on success and negative error code on failure */ int si476x_core_cmd_am_seek_start(struct si476x_core *core, bool seekup, bool wrap) { … } EXPORT_SYMBOL_GPL(…); static int si476x_core_cmd_power_up_a10(struct si476x_core *core, struct si476x_power_up_args *puargs) { … } static int si476x_core_cmd_power_up_a20(struct si476x_core *core, struct si476x_power_up_args *puargs) { … } static int si476x_core_cmd_power_down_a10(struct si476x_core *core, struct si476x_power_down_args *pdargs) { … } static int si476x_core_cmd_power_down_a20(struct si476x_core *core, struct si476x_power_down_args *pdargs) { … } static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core, struct si476x_tune_freq_args *tuneargs) { … } static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core, struct si476x_tune_freq_args *tuneargs) { … } static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core, struct si476x_rsq_status_args *rsqargs, struct si476x_rsq_status_report *report) { … } static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core, struct si476x_rsq_status_args *rsqargs, struct si476x_rsq_status_report *report) { … } static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core, struct si476x_rsq_status_args *rsqargs, struct si476x_rsq_status_report *report) { … } static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core, struct si476x_tune_freq_args *tuneargs) { … } static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core, struct si476x_tune_freq_args *tuneargs) { … } static int si476x_core_cmd_agc_status_a20(struct si476x_core *core, struct si476x_agc_status_report *report) { … } static int si476x_core_cmd_agc_status_a10(struct si476x_core *core, struct si476x_agc_status_report *report) { … } tune_freq_func_t; static struct { … } si476x_cmds_vtable[] = …; int si476x_core_cmd_power_up(struct si476x_core *core, struct si476x_power_up_args *args) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_power_down(struct si476x_core *core, struct si476x_power_down_args *args) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_fm_tune_freq(struct si476x_core *core, struct si476x_tune_freq_args *args) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_am_tune_freq(struct si476x_core *core, struct si476x_tune_freq_args *args) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_fm_rsq_status(struct si476x_core *core, struct si476x_rsq_status_args *args, struct si476x_rsq_status_report *report) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_agc_status(struct si476x_core *core, struct si476x_agc_status_report *report) { … } EXPORT_SYMBOL_GPL(…); int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core, enum si476x_intb_config intb, enum si476x_a1_config a1) { … } EXPORT_SYMBOL_GPL(…); MODULE_LICENSE(…) …; MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …;