diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/media/platform/msm/ais/common/msm_camera_io_util.c | 8 | ||||
| -rw-r--r-- | drivers/media/radio/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/media/radio/Makefile | 2 | ||||
| -rw-r--r-- | drivers/media/radio/silabs/Makefile | 1 | ||||
| -rw-r--r-- | drivers/media/radio/silabs/radio-silabs.c | 3963 | ||||
| -rw-r--r-- | drivers/media/radio/silabs/radio-silabs.h | 532 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-dev.c | 3 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-ioctl.c | 4 | ||||
| -rw-r--r-- | drivers/platform/msm/ipa/ipa_v2/ipa_rt.c | 10 | ||||
| -rw-r--r-- | drivers/power/supply/qcom/qpnp-smbcharger.c | 55 | ||||
| -rw-r--r-- | drivers/soc/qcom/ipc_router_mhi_xprt.c | 8 | ||||
| -rw-r--r-- | drivers/soc/qcom/smcinvoke.c | 6 | ||||
| -rw-r--r-- | drivers/tty/serial/msm_serial_hs.c | 4 |
13 files changed, 4590 insertions, 13 deletions
diff --git a/drivers/media/platform/msm/ais/common/msm_camera_io_util.c b/drivers/media/platform/msm/ais/common/msm_camera_io_util.c index c6c2e0d02b65..8cee210095eb 100644 --- a/drivers/media/platform/msm/ais/common/msm_camera_io_util.c +++ b/drivers/media/platform/msm/ais/common/msm_camera_io_util.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -357,12 +357,13 @@ int msm_cam_clk_enable(struct device *dev, struct msm_cam_clk_info *clk_info, } } else { for (i = num_clk - 1; i >= 0; i--) { - if (clk_ptr[i] != NULL) { + if (!IS_ERR_OR_NULL(clk_ptr[i])) { CDBG("%s disable %s\n", __func__, clk_info[i].clk_name); clk_disable(clk_ptr[i]); clk_unprepare(clk_ptr[i]); clk_put(clk_ptr[i]); + clk_ptr[i] = NULL; } } } @@ -378,10 +379,11 @@ cam_clk_set_err: clk_put(clk_ptr[i]); cam_clk_get_err: for (i--; i >= 0; i--) { - if (clk_ptr[i] != NULL) { + if (!IS_ERR_OR_NULL(clk_ptr[i])) { clk_disable(clk_ptr[i]); clk_unprepare(clk_ptr[i]); clk_put(clk_ptr[i]); + clk_ptr[i] = NULL; } } return rc; diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 192f36f2f4aa..8aac8074e4e1 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -498,4 +498,11 @@ config RADIO_ZOLTRIX_PORT endif # V4L_RADIO_ISA_DRIVERS +config RADIO_SILABS + tristate "SILABS FM" + depends on I2C && VIDEO_V4L2 + ---help--- + Say Y here if you want to use the SiLabs' FM chip + with I2C as transport. + endif # RADIO_ADAPTERS diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 120e791199b2..cc8eabb0d9c3 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -33,7 +33,7 @@ obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o obj-$(CONFIG_RADIO_WL128X) += wl128x/ obj-$(CONFIG_RADIO_TEA575X) += tea575x.o obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o - +obj-$(CONFIG_RADIO_SILABS) += silabs/ shark2-objs := radio-shark2.o radio-tea5777.o ccflags-y += -Isound diff --git a/drivers/media/radio/silabs/Makefile b/drivers/media/radio/silabs/Makefile new file mode 100644 index 000000000000..48d34fc4683d --- /dev/null +++ b/drivers/media/radio/silabs/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_RADIO_SILABS) += radio-silabs.o diff --git a/drivers/media/radio/silabs/radio-silabs.c b/drivers/media/radio/silabs/radio-silabs.c new file mode 100644 index 000000000000..5e41fa747896 --- /dev/null +++ b/drivers/media/radio/silabs/radio-silabs.c @@ -0,0 +1,3963 @@ +/* Copyright (c) 2014-2015, 2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#define DRIVER_NAME "radio-silabs" +#define DRIVER_CARD "Silabs FM Radio Receiver" +#define DRIVER_DESC "Driver for Silabs FM Radio receiver" + +#include <linux/version.h> +#include <linux/init.h> /* Initdata */ +#include <linux/delay.h> /* udelay */ +#include <linux/uaccess.h> /* copy to/from user */ +#include <linux/kfifo.h> /* lock free circular buffer */ +#include <linux/param.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> + +/* kernel includes */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/videodev2.h> +#include <linux/mutex.h> +#include <linux/unistd.h> +#include <linux/atomic.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/pwm.h> +#include <linux/regulator/consumer.h> +#include <linux/pinctrl/consumer.h> +#include <linux/clk.h> +#include <linux/of_gpio.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> +#include "radio-silabs.h" + +struct silabs_fm_device { + struct i2c_client *client; + struct pwm_device *pwm; + bool is_len_gpio_valid; + struct fm_power_vreg_data *dreg; + struct fm_power_vreg_data *areg; + int reset_gpio; + int int_gpio; + int status_gpio; + struct pinctrl *fm_pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + struct video_device *videodev; + struct v4l2_device v4l2_dev; + /* driver management */ + atomic_t users; + /* To send commands*/ + u8 write_buf[WRITE_REG_NUM]; + /* TO read events, data*/ + u8 read_buf[READ_REG_NUM]; + /*RDS buffers + Radio event buffer*/ + struct kfifo data_buf[SILABS_FM_BUF_MAX]; + struct silabs_fm_recv_conf_req recv_conf; + struct completion sync_req_done; + /* for the first tune, we need to set properties for digital audio. */ + u8 first_tune; + int tune_req; + /* 1 if tune is pending, 2 if seek is pending, 0 otherwise.*/ + u8 seek_tune_status; + /* command that is being sent to chip. */ + u8 cmd; + u8 antenna; + u8 g_search_mode; + bool is_search_cancelled; + unsigned int mode; + /* regional settings */ + enum silabs_region_t region; + /* power mode */ + bool lp_mode; + int handle_irq; + /* global lock */ + struct mutex lock; + /* buffer locks*/ + spinlock_t buf_lock[SILABS_FM_BUF_MAX]; + /* work queue */ + struct workqueue_struct *wqueue; + struct workqueue_struct *wqueue_scan; + struct workqueue_struct *wqueue_af; + struct workqueue_struct *wqueue_rds; + struct work_struct rds_worker; + struct delayed_work work; + struct delayed_work work_scan; + struct delayed_work work_af; + /* wait queue for blocking event read */ + wait_queue_head_t event_queue; + /* wait queue for raw rds read */ + wait_queue_head_t read_queue; + int irq; + int status_irq; + int tuned_freq_khz; + int dwell_time_sec; + u16 pi; /* PI of tuned channel */ + u8 pty; /* programe type of the tuned channel */ + u16 block[NO_OF_RDS_BLKS]; + u8 rt_display[MAX_RT_LEN]; /* RT that will be displayed */ + u8 rt_tmp0[MAX_RT_LEN]; /* high probability RT */ + u8 rt_tmp1[MAX_RT_LEN]; /* low probability RT */ + u8 rt_cnt[MAX_RT_LEN]; /* high probability RT's hit count */ + u8 rt_flag; /* A/B flag of RT */ + bool valid_rt_flg; /* validity of A/B flag */ + u8 ps_display[MAX_PS_LEN]; /* PS that will be displayed */ + u8 ps_tmp0[MAX_PS_LEN]; /* high probability PS */ + u8 ps_tmp1[MAX_PS_LEN]; /* low probability PS */ + u8 ps_cnt[MAX_PS_LEN]; /* high probability PS's hit count */ + u8 rt_plus_carrier; + u8 ert_carrier; + u8 ert_buf[MAX_ERT_LEN]; + u8 ert_len; + u8 c_byt_pair_index; + u8 utf_8_flag; + u8 rt_ert_flag; + u8 formatting_dir; + bool is_af_jump_enabled; + bool is_af_tune_in_progress; + u16 af_avg_th; + u8 af_wait_timer; + u8 af_rssi_th; /* allowed rssi is 0-127 */ + u8 rssi_th; /* 0 - 127 */ + u8 sinr_th; /* 0 - 127 */ + u8 rds_fifo_cnt; /* 0 - 25 */ + struct silabs_af_info af_info1; + struct silabs_af_info af_info2; + struct silabs_srch_list_compl srch_list; +}; + +static int silabs_fm_request_irq(struct silabs_fm_device *radio); +static int tune(struct silabs_fm_device *radio, u32 freq); +static int silabs_seek(struct silabs_fm_device *radio, int dir, int wrap); +static int cancel_seek(struct silabs_fm_device *radio); +static int configure_interrupts(struct silabs_fm_device *radio, u8 val); +static void silabs_fm_q_event(struct silabs_fm_device *radio, + enum silabs_evt_t event); + +static bool is_valid_rssi(int rssi) +{ + if ((rssi >= MIN_RSSI) && + (rssi <= MAX_RSSI)) + return true; + else + return false; +} + +static bool is_valid_sinr(int sinr) +{ + if ((sinr >= MIN_SNR) && + (sinr <= MAX_SNR)) + return true; + else + return false; +} + +static bool is_valid_rds_fifo_cnt(int cnt) +{ + if ((cnt >= MIN_RDS_FIFO_CNT) && + (cnt <= MAX_RDS_FIFO_CNT)) + return true; + else + return false; +} + +static int silabs_fm_i2c_read(struct silabs_fm_device *radio, u8 len) +{ + int i = 0, retval = 0; + struct i2c_msg msgs[1]; + + msgs[0].addr = radio->client->addr; + msgs[0].len = len; + msgs[0].flags = I2C_M_RD; + msgs[0].buf = (u8 *)radio->read_buf; + + for (i = 0; i < 2; i++) { + retval = i2c_transfer(radio->client->adapter, msgs, 1); + if (retval == 1) + break; + } + + return retval; +} + +static int silabs_fm_i2c_write(struct silabs_fm_device *radio, u8 len) +{ + struct i2c_msg msgs[1]; + int i = 0, retval = 0; + + msgs[0].addr = radio->client->addr; + msgs[0].len = len; + msgs[0].flags = 0; + msgs[0].buf = (u8 *)radio->write_buf; + + for (i = 0; i < 2; i++) { + retval = i2c_transfer(radio->client->adapter, msgs, 1); + if (retval == 1) + break; + } + + return retval; +} + +static int silabs_fm_pinctrl_select(struct silabs_fm_device *radio, bool on) +{ + struct pinctrl_state *pins_state; + int ret; + + pins_state = on ? radio->gpio_state_active + : radio->gpio_state_suspend; + + if (!IS_ERR_OR_NULL(pins_state)) { + ret = pinctrl_select_state(radio->fm_pinctrl, pins_state); + if (ret) { + FMDERR("%s: cannot set pin state\n", __func__); + return ret; + } + } else { + FMDERR("%s: not a valid %s pin state\n", __func__, + on ? "pmx_fm_active" : "pmx_fm_suspend"); + } + + return 0; +} + +static int fm_configure_gpios(struct silabs_fm_device *radio, bool on) +{ + int rc = 0; + int fm_reset_gpio = radio->reset_gpio; + int fm_int_gpio = radio->int_gpio; + int fm_status_gpio = radio->status_gpio; + + if (on) { + /* + * Turn ON sequence + * GPO1/status gpio configuration. + * Keep the GPO1 to high till device comes out of reset. + */ + if (fm_status_gpio > 0) { + FMDERR("status gpio is provided, setting it to high\n"); + rc = gpio_direction_output(fm_status_gpio, 1); + if (rc) { + FMDERR("unable to set gpio %d direction(%d)\n", + fm_status_gpio, rc); + return rc; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + } + + /* + * GPO2/Interrupt gpio configuration. + * Keep the GPO2 to low till device comes out of reset. + */ + rc = gpio_direction_output(fm_int_gpio, 0); + if (rc) { + FMDERR("unable to set the gpio %d direction(%d)\n", + fm_int_gpio, rc); + return rc; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + + /* + * Reset pin configuration. + * write "0'' to make sure the chip is in reset. + */ + rc = gpio_direction_output(fm_reset_gpio, 0); + if (rc) { + FMDERR("Unable to set direction\n"); + return rc; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + /* write "1" to bring the chip out of reset.*/ + rc = gpio_direction_output(fm_reset_gpio, 1); + if (rc) { + FMDERR("Unable to set direction\n"); + return rc; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + + rc = gpio_direction_input(fm_int_gpio); + if (rc) { + FMDERR("unable to set the gpio %d direction(%d)\n", + fm_int_gpio, rc); + return rc; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + + if (fm_status_gpio > 0) { + FMDERR("setting status gpio as input\n"); + rc = gpio_direction_input(fm_status_gpio); + if (rc) { + FMDERR("unable to set gpio %d direction(%d)\n", + fm_status_gpio, rc); + return rc; + } + /* Wait for the value to take effect on gpio. */ + msleep(100); + } + + + } else { + /*Turn OFF sequence */ + gpio_set_value(fm_reset_gpio, 0); + + rc = gpio_direction_input(fm_reset_gpio); + if (rc) + FMDERR("Unable to set direction\n"); + /* Wait for some time for the value to take effect. */ + msleep(100); + if (fm_status_gpio > 0) { + rc = gpio_direction_input(fm_status_gpio); + if (rc) + FMDERR("Unable to set dir for status gpio\n"); + msleep(100); + } + } + return rc; +} + +static int silabs_fm_areg_cfg(struct silabs_fm_device *radio, bool on) +{ + int rc = 0; + struct fm_power_vreg_data *vreg; + + vreg = radio->areg; + if (!vreg) { + FMDERR("In %s, areg is NULL\n", __func__); + return rc; + } + if (on) { + FMDBG("vreg is : %s", vreg->name); + if (vreg->set_voltage_sup) { + rc = regulator_set_voltage(vreg->reg, + vreg->low_vol_level, + vreg->high_vol_level); + if (rc < 0) { + FMDERR("set_vol(%s) fail %d\n", vreg->name, rc); + return rc; + } + } + rc = regulator_enable(vreg->reg); + if (rc < 0) { + FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc); + if (vreg->set_voltage_sup) { + regulator_set_voltage(vreg->reg, + 0, + vreg->high_vol_level); + } + return rc; + } + vreg->is_enabled = true; + + } else { + rc = regulator_disable(vreg->reg); + if (rc < 0) { + FMDERR("reg disable(%s) fail rc=%d\n", vreg->name, rc); + return rc; + } + vreg->is_enabled = false; + + if (vreg->set_voltage_sup) { + /* Set the min voltage to 0 */ + rc = regulator_set_voltage(vreg->reg, + 0, + vreg->high_vol_level); + if (rc < 0) { + FMDERR("set_vol(%s) fail %d\n", vreg->name, rc); + return rc; + } + } + } + return rc; +} + +static int silabs_fm_dreg_cfg(struct silabs_fm_device *radio, bool on) +{ + int rc = 0; + struct fm_power_vreg_data *vreg; + + vreg = radio->dreg; + if (!vreg) { + FMDERR("In %s, dreg is NULL\n", __func__); + return rc; + } + + if (on) { + FMDBG("vreg is : %s", vreg->name); + if (vreg->set_voltage_sup) { + rc = regulator_set_voltage(vreg->reg, + vreg->low_vol_level, + vreg->high_vol_level); + if (rc < 0) { + FMDERR("set_vol(%s) fail %d\n", vreg->name, rc); + return rc; + } + } + + rc = regulator_enable(vreg->reg); + if (rc < 0) { + FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc); + if (vreg->set_voltage_sup) { + regulator_set_voltage(vreg->reg, + 0, + vreg->high_vol_level); + } + return rc; + } + vreg->is_enabled = true; + } else { + rc = regulator_disable(vreg->reg); + if (rc < 0) { + FMDERR("reg disable(%s) fail. rc=%d\n", vreg->name, rc); + return rc; + } + vreg->is_enabled = false; + + if (vreg->set_voltage_sup) { + /* Set the min voltage to 0 */ + rc = regulator_set_voltage(vreg->reg, + 0, + vreg->high_vol_level); + if (rc < 0) { + FMDERR("set_vol(%s) fail %d\n", vreg->name, rc); + return rc; + } + } + } + return rc; +} + +static int silabs_fm_power_cfg(struct silabs_fm_device *radio, bool on) +{ + int rc = 0; + + if (on) { + /* Turn ON sequence */ + rc = silabs_fm_dreg_cfg(radio, on); + if (rc < 0) { + FMDERR("In %s, dreg cfg failed %x\n", __func__, rc); + return rc; + } + rc = silabs_fm_areg_cfg(radio, on); + if (rc < 0) { + FMDERR("In %s, areg cfg failed %x\n", __func__, rc); + silabs_fm_dreg_cfg(radio, false); + return rc; + } + /* If pinctrl is supported, select active state */ + if (radio->fm_pinctrl) { + rc = silabs_fm_pinctrl_select(radio, true); + if (rc) + FMDERR("%s: error setting active pin state\n", + __func__); + } + + rc = fm_configure_gpios(radio, on); + if (rc < 0) { + FMDERR("fm_power gpio config failed\n"); + silabs_fm_dreg_cfg(radio, false); + silabs_fm_areg_cfg(radio, false); + return rc; + } + } else { + /* Turn OFF sequence */ + rc = fm_configure_gpios(radio, on); + if (rc < 0) + FMDERR("fm_power gpio config failed"); + + /* If pinctrl is supported, select suspend state */ + if (radio->fm_pinctrl) { + rc = silabs_fm_pinctrl_select(radio, false); + if (rc) + FMDERR("%s: error setting suspend pin state\n", + __func__); + } + rc = silabs_fm_dreg_cfg(radio, on); + if (rc < 0) + FMDERR("In %s, dreg cfg failed %x\n", __func__, rc); + rc = silabs_fm_areg_cfg(radio, on); + if (rc < 0) + FMDERR("In %s, areg cfg failed %x\n", __func__, rc); + } + return rc; +} + +static bool is_enable_rx_possible(struct silabs_fm_device *radio) +{ + bool retval = true; + + if (radio->mode == FM_OFF || radio->mode == FM_RECV) + retval = false; + + return retval; +} + +static int read_cts_bit(struct silabs_fm_device *radio) +{ + int retval = 1, i = 0; + + for (i = 0; i < CTS_RETRY_COUNT; i++) { + memset(radio->read_buf, 0, READ_REG_NUM); + + retval = silabs_fm_i2c_read(radio, READ_REG_NUM); + + if (retval < 0) { + FMDERR("%s: failure reading the response, error %d\n", + __func__, retval); + continue; + } else + FMDBG("%s: successfully read the response from soc\n", + __func__); + + if (radio->read_buf[0] & ERR_BIT_MASK) { + FMDERR("%s: error bit set\n", __func__); + switch (radio->read_buf[1]) { + case BAD_CMD: + FMDERR("%s: cmd %d, error BAD_CMD\n", + __func__, radio->cmd); + break; + case BAD_ARG1: + FMDERR("%s: cmd %d, error BAD_ARG1\n", + __func__, radio->cmd); + break; + case BAD_ARG2: + FMDERR("%s: cmd %d, error BAD_ARG2\n", + __func__, radio->cmd); + break; + case BAD_ARG3: + FMDERR("%s: cmd %d, error BAD_ARG3\n", + __func__, radio->cmd); + break; + case BAD_ARG4: + FMDERR("%s: cmd %d, error BAD_ARG4\n", + __func__, radio->cmd); + break; + case BAD_ARG5: + FMDERR("%s: cmd %d, error BAD_ARG5\n", + __func__, radio->cmd); + case BAD_ARG6: + FMDERR("%s: cmd %d, error BAD_ARG6\n", + __func__, radio->cmd); + break; + case BAD_ARG7: + FMDERR("%s: cmd %d, error BAD_ARG7\n", + __func__, radio->cmd); + break; + case BAD_PROP: + FMDERR("%s: cmd %d, error BAD_PROP\n", + __func__, radio->cmd); + break; + case BAD_BOOT_MODE: + FMDERR("%s:cmd %d,err BAD_BOOT_MODE\n", + __func__, radio->cmd); + break; + default: + FMDERR("%s: cmd %d, unknown error\n", + __func__, radio->cmd); + break; + } + retval = -EINVAL; + goto bad_cmd_arg; + + } + + if (radio->read_buf[0] & CTS_INT_BIT_MASK) { + FMDBG("In %s, CTS bit is set\n", __func__); + break; + } + /* + * Give some time if the chip is not done with processing + * previous command. + */ + msleep(100); + } + + FMDBG("In %s, status byte is %x\n", __func__, radio->read_buf[0]); + +bad_cmd_arg: + return retval; +} + +static int send_cmd(struct silabs_fm_device *radio, u8 total_len) +{ + int retval = 0; + + retval = silabs_fm_i2c_write(radio, total_len); + + if (retval > 0) { + FMDBG("In %s, successfully written command %x to soc\n", + __func__, radio->write_buf[0]); + } else { + FMDERR("In %s, error %d writing command %d to soc\n", + __func__, retval, radio->write_buf[1]); + } + + retval = read_cts_bit(radio); + + return retval; +} + +static int get_property(struct silabs_fm_device *radio, u16 prop, u16 *pvalue) +{ + int retval = 0; + + mutex_lock(&radio->lock); + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = GET_PROPERTY_CMD; + radio->write_buf[0] = GET_PROPERTY_CMD; + /* reserved, always write 0 */ + radio->write_buf[1] = 0; + /* property high byte */ + radio->write_buf[2] = HIGH_BYTE_16BIT(prop); + /* property low byte */ + radio->write_buf[3] = LOW_BYTE_16BIT(prop); + + FMDBG("in %s, radio->write_buf[2] is %x\n", + __func__, radio->write_buf[2]); + FMDBG("in %s, radio->write_buf[3] is %x\n", + __func__, radio->write_buf[3]); + + retval = send_cmd(radio, GET_PROP_CMD_LEN); + if (retval < 0) + FMDERR("In %s, error getting property %d\n", __func__, prop); + else + *pvalue = (radio->read_buf[2] << 8) + radio->read_buf[3]; + + mutex_unlock(&radio->lock); + + return retval; +} + + +static int set_property(struct silabs_fm_device *radio, u16 prop, u16 value) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = SET_PROPERTY_CMD; + radio->write_buf[0] = SET_PROPERTY_CMD; + /* reserved, always write 0 */ + radio->write_buf[1] = 0; + /* property high byte */ + radio->write_buf[2] = HIGH_BYTE_16BIT(prop); + /* property low byte */ + radio->write_buf[3] = LOW_BYTE_16BIT(prop); + + /* value high byte */ + radio->write_buf[4] = HIGH_BYTE_16BIT(value); + /* value low byte */ + radio->write_buf[5] = LOW_BYTE_16BIT(value); + + retval = send_cmd(radio, SET_PROP_CMD_LEN); + if (retval < 0) + FMDERR("In %s, error setting property %d\n", __func__, prop); + + mutex_unlock(&radio->lock); + + return retval; +} + +static void update_search_list(struct silabs_fm_device *radio, int freq) +{ + int temp_freq = freq; + + temp_freq = temp_freq - + (radio->recv_conf.band_low_limit * TUNE_STEP_SIZE); + temp_freq = temp_freq / 50; + radio->srch_list.rel_freq[radio->srch_list.num_stations_found]. + rel_freq_lsb = GET_LSB(temp_freq); + radio->srch_list.rel_freq[radio->srch_list.num_stations_found]. + rel_freq_msb = GET_MSB(temp_freq); + radio->srch_list.num_stations_found++; +} + +static void silabs_scan(struct work_struct *work) +{ + struct silabs_fm_device *radio; + int current_freq_khz; + u8 valid; + u8 bltf; + u32 temp_freq_khz; + int retval = 0; + struct kfifo *data_b; + int len = 0; + + FMDBG("+%s, getting radio handle from work struct\n", __func__); + radio = container_of(work, struct silabs_fm_device, work_scan.work); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return; + } + + current_freq_khz = radio->tuned_freq_khz; + FMDBG("current freq is %d\n", current_freq_khz); + + radio->seek_tune_status = SCAN_PENDING; + /* tune to lowest freq of the band */ + retval = tune(radio, radio->recv_conf.band_low_limit * TUNE_STEP_SIZE); + if (retval < 0) { + FMDERR("%s: Tune to lower band limit failed with error %d\n", + __func__, retval); + goto seek_tune_fail; + } + + /* wait for tune to complete. */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) + FMDERR("In %s, didn't receive STC for tune\n", __func__); + else + FMDBG("In %s, received STC for tune\n", __func__); + while (1) { + /* If scan is cancelled or FM is not ON, break */ + if (radio->is_search_cancelled == true) { + FMDBG("%s: scan cancelled\n", __func__); + if (radio->g_search_mode == SCAN_FOR_STRONG) + goto seek_tune_fail; + else + goto seek_cancelled; + } else if (radio->mode != FM_RECV) { + FMDERR("%s: FM is not in proper state\n", __func__); + return; + } + + retval = silabs_seek(radio, SRCH_DIR_UP, WRAP_DISABLE); + if (retval < 0) { + FMDERR("Scan operation failed with error %d\n", retval); + goto seek_tune_fail; + } + /* wait for seek to complete */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) { + FMDERR("%s: didn't receive STC for seek\n", __func__); + /* FM is not correct state or scan is cancelled */ + continue; + } else + FMDBG("%s: received STC for seek\n", __func__); + + mutex_lock(&radio->lock); + memset(radio->write_buf, 0, WRITE_REG_NUM); + + radio->cmd = FM_TUNE_STATUS_CMD; + + radio->write_buf[0] = FM_TUNE_STATUS_CMD; + radio->write_buf[1] = 0; + + retval = send_cmd(radio, TUNE_STATUS_CMD_LEN); + if (retval < 0) { + FMDERR("%s: FM_TUNE_STATUS_CMD failed with error %d\n", + __func__, retval); + } + + valid = radio->read_buf[1] & VALID_MASK; + bltf = radio->read_buf[1] & BLTF_MASK; + + temp_freq_khz = ((u32)(radio->read_buf[2] << 8) + + radio->read_buf[3])* + TUNE_STEP_SIZE; + mutex_unlock(&radio->lock); + FMDBG("In %s, freq is %d\n", __func__, temp_freq_khz); + + if ((valid) && (radio->g_search_mode == SCAN)) { + FMDBG("val bit set, posting SILABS_EVT_TUNE_SUCC\n"); + silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC); + } + + if (bltf) { + FMDBG("bltf bit is set\n"); + break; + } + /* + * If scan is cancelled or FM is not ON, break ASAP so that we + * don't need to sleep for dwell time. + */ + if (radio->is_search_cancelled == true) { + FMDBG("%s: scan cancelled\n", __func__); + if (radio->g_search_mode == SCAN_FOR_STRONG) + goto seek_tune_fail; + else + goto seek_cancelled; + } else if (radio->mode != FM_RECV) { + FMDERR("%s: FM is not in proper state\n", __func__); + return; + } + + if (radio->g_search_mode == SCAN) { + /* sleep for dwell period */ + msleep(radio->dwell_time_sec * 1000); + /* need to queue the event when the seek completes */ + silabs_fm_q_event(radio, SILABS_EVT_SCAN_NEXT); + } else if ((valid) && + (radio->g_search_mode == SCAN_FOR_STRONG)) { + update_search_list(radio, temp_freq_khz); + } + } + +seek_tune_fail: + if (radio->g_search_mode == SCAN_FOR_STRONG) { + len = radio->srch_list.num_stations_found * 2 + + sizeof(radio->srch_list.num_stations_found); + data_b = &radio->data_buf[SILABS_FM_BUF_SRCH_LIST]; + kfifo_in_locked(data_b, &radio->srch_list, len, + &radio->buf_lock[SILABS_FM_BUF_SRCH_LIST]); + silabs_fm_q_event(radio, SILABS_EVT_NEW_SRCH_LIST); + } + /* tune to original frequency */ + retval = tune(radio, current_freq_khz); + if (retval < 0) + FMDERR("%s: Tune to orig freq failed with error %d\n", + __func__, retval); + else { + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) + FMDERR("%s: didn't receive STC for tune\n", __func__); + else + FMDBG("%s: received STC for tune\n", __func__); + } +seek_cancelled: + silabs_fm_q_event(radio, SILABS_EVT_SEEK_COMPLETE); + radio->seek_tune_status = NO_SEEK_TUNE_PENDING; +} + +static void silabs_search(struct silabs_fm_device *radio, bool on) +{ + int current_freq_khz; + + current_freq_khz = radio->tuned_freq_khz; + + if (on) { + FMDBG("%s: Queuing the work onto scan work q\n", __func__); + queue_delayed_work(radio->wqueue_scan, &radio->work_scan, + msecs_to_jiffies(SILABS_DELAY_MSEC)); + } else { + cancel_seek(radio); + silabs_fm_q_event(radio, SILABS_EVT_SEEK_COMPLETE); + } +} + +static void get_rds_status(struct silabs_fm_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + memset(radio->write_buf, 0, WRITE_REG_NUM); + radio->cmd = FM_RDS_STATUS_CMD; + radio->write_buf[0] = FM_RDS_STATUS_CMD; + radio->write_buf[1] |= FM_RDS_STATUS_IN_INTACK; + + retval = send_cmd(radio, RDS_CMD_LEN); + if (retval < 0) { + FMDERR("In %s, Get RDS failed %d\n", __func__, retval); + mutex_unlock(&radio->lock); + return; + } + + memset(radio->read_buf, 0, sizeof(radio->read_buf)); + + retval = silabs_fm_i2c_read(radio, RDS_RSP_LEN); + + if (retval < 0) { + FMDERR("In %s, failed to read the resp from soc %d\n", + __func__, retval); + mutex_unlock(&radio->lock); + return; + } + FMDBG("In %s, successfully read the response from soc\n", + __func__); + + radio->block[0] = ((u16)radio->read_buf[MSB_OF_BLK_0] << 8) | + (u16)radio->read_buf[LSB_OF_BLK_0]; + radio->block[1] = ((u16)radio->read_buf[MSB_OF_BLK_1] << 8) | + (u16)radio->read_buf[LSB_OF_BLK_1]; + radio->block[2] = ((u16)radio->read_buf[MSB_OF_BLK_2] << 8) | + (u16)radio->read_buf[LSB_OF_BLK_2]; + radio->block[3] = ((u16)radio->read_buf[MSB_OF_BLK_3] << 8) | + (u16)radio->read_buf[LSB_OF_BLK_3]; + mutex_unlock(&radio->lock); +} + +static void pi_handler(struct silabs_fm_device *radio, u16 current_pi) +{ + if (radio->pi != current_pi) { + FMDBG("PI code of radio->block[0] = %x\n", current_pi); + radio->pi = current_pi; + } else { + FMDBG(" Received same PI code\n"); + } +} + +static void pty_handler(struct silabs_fm_device *radio, u8 current_pty) +{ + if (radio->pty != current_pty) { + FMDBG("PTY code of radio->block[1] = %x\n", current_pty); + radio->pty = current_pty; + } else { + FMDBG("PTY repeated\n"); + } +} + +static void update_ps(struct silabs_fm_device *radio, u8 addr, u8 ps) +{ + u8 i; + bool ps_txt_chg = false; + bool ps_cmplt = true; + u8 *data; + struct kfifo *data_b; + + if (radio->ps_tmp0[addr] == ps) { + if (radio->ps_cnt[addr] < PS_VALIDATE_LIMIT) { + radio->ps_cnt[addr]++; + } else { + radio->ps_cnt[addr] = PS_VALIDATE_LIMIT; + radio->ps_tmp1[addr] = ps; + } + } else if (radio->ps_tmp1[addr] == ps) { + if (radio->ps_cnt[addr] >= PS_VALIDATE_LIMIT) { + ps_txt_chg = true; + radio->ps_cnt[addr] = PS_VALIDATE_LIMIT + 1; + } else { + radio->ps_cnt[addr] = PS_VALIDATE_LIMIT; + } + radio->ps_tmp1[addr] = radio->ps_tmp0[addr]; + radio->ps_tmp0[addr] = ps; + } else if (!radio->ps_cnt[addr]) { + radio->ps_tmp0[addr] = ps; + radio->ps_cnt[addr] = 1; + } else { + radio->ps_tmp1[addr] = ps; + } + + if (ps_txt_chg) { + for (i = 0; i < MAX_PS_LEN; i++) { + if (radio->ps_cnt[i] > 1) + radio->ps_cnt[i]--; + } + } + + for (i = 0; i < MAX_PS_LEN; i++) { + if (radio->ps_cnt[i] < PS_VALIDATE_LIMIT) { + ps_cmplt = false; + return; + } + } + + if (ps_cmplt) { + for (i = 0; (i < MAX_PS_LEN) && + (radio->ps_display[i] == radio->ps_tmp0[i]); i++) + ; + + if (i == MAX_PS_LEN) { + FMDBG("Same PS string repeated\n"); + return; + } + + for (i = 0; i < MAX_PS_LEN; i++) + radio->ps_display[i] = radio->ps_tmp0[i]; + + data = kmalloc(PS_EVT_DATA_LEN, GFP_ATOMIC); + if (data != NULL) { + data[0] = NO_OF_PS; + data[1] = radio->pty; + data[2] = (radio->pi >> 8) & 0xFF; + data[3] = (radio->pi & 0xFF); + data[4] = 0; + memcpy(data + OFFSET_OF_PS, + radio->ps_tmp0, MAX_PS_LEN); + data_b = &radio->data_buf[SILABS_FM_BUF_PS_RDS]; + kfifo_in_locked(data_b, data, PS_EVT_DATA_LEN, + &radio->buf_lock[SILABS_FM_BUF_PS_RDS]); + FMDBG("Q the PS event\n"); + silabs_fm_q_event(radio, SILABS_EVT_NEW_PS_RDS); + kfree(data); + } else { + FMDERR("Memory allocation failed for PTY\n"); + } + } +} + +static void display_rt(struct silabs_fm_device *radio) +{ + u8 len = 0, i = 0; + u8 *data; + struct kfifo *data_b; + bool rt_cmplt = true; + + for (i = 0; i < MAX_RT_LEN; i++) { + if (radio->rt_cnt[i] < RT_VALIDATE_LIMIT) { + rt_cmplt = false; + return; + } + if (radio->rt_tmp0[i] == END_OF_RT) + break; + } + + if (rt_cmplt) { + while ((len < MAX_RT_LEN) && (radio->rt_tmp0[len] != END_OF_RT)) + len++; + + for (i = 0; (i < len) && + (radio->rt_display[i] == radio->rt_tmp0[i]); i++) + ; + + if (i == len) { + FMDBG("Same RT string repeated\n"); + return; + } + for (i = 0; i < len; i++) + radio->rt_display[i] = radio->rt_tmp0[i]; + data = kmalloc(len + OFFSET_OF_RT, GFP_ATOMIC); + if (data != NULL) { + data[0] = len; /* len of RT */ + data[1] = radio->pty; + data[2] = (radio->pi >> 8) & 0xFF; + data[3] = (radio->pi & 0xFF); + data[4] = radio->rt_flag; + memcpy(data + OFFSET_OF_RT, radio->rt_display, len); + data_b = &radio->data_buf[SILABS_FM_BUF_RT_RDS]; + kfifo_in_locked(data_b, data, OFFSET_OF_RT + len, + &radio->buf_lock[SILABS_FM_BUF_RT_RDS]); + FMDBG("Q the RT event\n"); + silabs_fm_q_event(radio, SILABS_EVT_NEW_RT_RDS); + kfree(data); + } else { + FMDERR("Memory allocation failed for PTY\n"); + } + } +} + +static void rt_handler(struct silabs_fm_device *radio, u8 ab_flg, + u8 cnt, u8 addr, u8 *rt) +{ + u8 i; + bool rt_txt_chg = 0; + + if (ab_flg != radio->rt_flag && radio->valid_rt_flg) { + for (i = 0; i < sizeof(radio->rt_cnt); i++) { + if (!radio->rt_tmp0[i]) { + radio->rt_tmp0[i] = ' '; + radio->rt_cnt[i]++; + } + } + memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt)); + memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0)); + memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1)); + } + + radio->rt_flag = ab_flg; + radio->valid_rt_flg = true; + + for (i = 0; i < cnt; i++) { + if (radio->rt_tmp0[addr+i] == rt[i]) { + if (radio->rt_cnt[addr+i] < RT_VALIDATE_LIMIT) { + radio->rt_cnt[addr+i]++; + } else { + radio->rt_cnt[addr+i] = RT_VALIDATE_LIMIT; + radio->rt_tmp1[addr+i] = rt[i]; + } + } else if (radio->rt_tmp1[addr+i] == rt[i]) { + if (radio->rt_cnt[addr+i] >= RT_VALIDATE_LIMIT) { + rt_txt_chg = true; + radio->rt_cnt[addr+i] = RT_VALIDATE_LIMIT + 1; + } else { + radio->rt_cnt[addr+i] = RT_VALIDATE_LIMIT; + } + radio->rt_tmp1[addr+i] = radio->rt_tmp0[addr+i]; + radio->rt_tmp0[addr+i] = rt[i]; + } else if (!radio->rt_cnt[addr+i]) { + radio->rt_tmp0[addr+i] = rt[i]; + radio->rt_cnt[addr+i] = 1; + } else { + radio->rt_tmp1[addr+i] = rt[i]; + } + } + + if (rt_txt_chg) { + for (i = 0; i < MAX_RT_LEN; i++) { + if (radio->rt_cnt[i] > 1) + radio->rt_cnt[i]--; + } + } + display_rt(radio); +} + +static void silabs_ev_ert(struct silabs_fm_device *radio) +{ + u8 *data = NULL; + struct kfifo *data_b; + + if (radio->ert_len <= 0) + return; + + data = kmalloc((radio->ert_len + ERT_OFFSET), GFP_ATOMIC); + if (data != NULL) { + data[0] = radio->ert_len; + data[1] = radio->utf_8_flag; + data[2] = radio->formatting_dir; + memcpy((data + ERT_OFFSET), radio->ert_buf, radio->ert_len); + data_b = &radio->data_buf[SILABS_FM_BUF_ERT]; + kfifo_in_locked(data_b, data, (radio->ert_len + ERT_OFFSET), + &radio->buf_lock[SILABS_FM_BUF_ERT]); + silabs_fm_q_event(radio, SILABS_EVT_NEW_ERT); + kfree(data); + } +} + +static void silabs_buff_ert(struct silabs_fm_device *radio) +{ + int i; + u16 info_byte = 0; + u8 byte_pair_index; + + byte_pair_index = radio->block[1] & APP_GRP_typ_MASK; + if (byte_pair_index == 0) { + radio->c_byt_pair_index = 0; + radio->ert_len = 0; + } + FMDBG("c_byt_pair_index = %x\n", radio->c_byt_pair_index); + if (radio->c_byt_pair_index == byte_pair_index) { + for (i = 2; i <= 3; i++) { + info_byte = radio->block[i]; + FMDBG("info_byte = %x\n", info_byte); + FMDBG("ert_len = %x\n", radio->ert_len); + if (radio->ert_len > (MAX_ERT_LEN - 2)) + return; + radio->ert_buf[radio->ert_len] = radio->block[i] >> 8; + radio->ert_buf[radio->ert_len + 1] = + radio->block[i] & 0xFF; + radio->ert_len += ERT_CNT_PER_BLK; + FMDBG("utf_8_flag = %d\n", radio->utf_8_flag); + if ((radio->utf_8_flag == 0) && + (info_byte == END_OF_RT)) { + radio->ert_len -= ERT_CNT_PER_BLK; + break; + } else if ((radio->utf_8_flag == 1) && + (radio->block[i] >> 8 == END_OF_RT)) { + info_byte = END_OF_RT; + radio->ert_len -= ERT_CNT_PER_BLK; + break; + } else if ((radio->utf_8_flag == 1) && + ((radio->block[i] & 0xFF) + == END_OF_RT)) { + info_byte = END_OF_RT; + radio->ert_len--; + break; + } + } + if ((byte_pair_index == MAX_ERT_SEGMENT) || + (info_byte == END_OF_RT)) { + silabs_ev_ert(radio); + radio->c_byt_pair_index = 0; + radio->ert_len = 0; + } + radio->c_byt_pair_index++; + } else { + radio->ert_len = 0; + radio->c_byt_pair_index = 0; + } +} + + +static void silabs_rt_plus(struct silabs_fm_device *radio) +{ + u8 tag_type1, tag_type2; + u8 *data = NULL; + int len = 0; + u16 grp_typ; + struct kfifo *data_b; + + grp_typ = radio->block[1] & APP_GRP_typ_MASK; + /* + *right most 3 bits of Lsb of block 2 + * and left most 3 bits of Msb of block 3 + */ + tag_type1 = (((grp_typ & TAG1_MSB_MASK) << TAG1_MSB_OFFSET) | + (radio->block[2] >> TAG1_LSB_OFFSET)); + /* + *right most 1 bit of lsb of 3rd block + * and left most 5 bits of Msb of 4th block + */ + tag_type2 = (((radio->block[2] & TAG2_MSB_MASK) + << TAG2_MSB_OFFSET) | + (radio->block[2] >> TAG2_LSB_OFFSET)); + + if (tag_type1 != DUMMY_CLASS) + len += RT_PLUS_LEN_1_TAG; + if (tag_type2 != DUMMY_CLASS) + len += RT_PLUS_LEN_1_TAG; + + if (len != 0) { + len += RT_PLUS_OFFSET; + data = kmalloc(len, GFP_ATOMIC); + } else { + FMDERR("%s:Len is zero\n", __func__); + return; + } + if (data != NULL) { + data[0] = len; + len = RT_ERT_FLAG_OFFSET; + data[len++] = radio->rt_ert_flag; + if (tag_type1 != DUMMY_CLASS) { + data[len++] = tag_type1; + /* + *start position of tag1 + *right most 5 bits of msb of 3rd block + *and left most bit of lsb of 3rd block + */ + data[len++] = (radio->block[2] >> TAG1_POS_LSB_OFFSET) + & TAG1_POS_MSB_MASK; + /* + *length of tag1 + *left most 6 bits of lsb of 3rd block + */ + data[len++] = (radio->block[2] >> TAG1_LEN_OFFSET) & + TAG1_LEN_MASK; + } + if (tag_type2 != DUMMY_CLASS) { + data[len++] = tag_type2; + /* + *start position of tag2 + *right most 3 bit of msb of 4th block + *and left most 3 bits of lsb of 4th block + */ + data[len++] = (radio->block[3] >> TAG2_POS_LSB_OFFSET) & + TAG2_POS_MSB_MASK; + /* + *length of tag2 + *right most 5 bits of lsb of 4th block + */ + data[len++] = radio->block[3] & TAG2_LEN_MASK; + } + data_b = &radio->data_buf[SILABS_FM_BUF_RT_PLUS]; + kfifo_in_locked(data_b, data, len, + &radio->buf_lock[SILABS_FM_BUF_RT_PLUS]); + silabs_fm_q_event(radio, SILABS_EVT_NEW_RT_PLUS); + kfree(data); + } else { + FMDERR("%s:memory allocation failed\n", __func__); + } +} + +static void silabs_raw_rds_handler(struct silabs_fm_device *radio) +{ + u16 aid, app_grp_typ; + + aid = radio->block[3]; + app_grp_typ = radio->block[1] & APP_GRP_typ_MASK; + FMDBG("app_grp_typ = %x\n", app_grp_typ); + FMDBG("AID = %x", aid); + + switch (aid) { + case ERT_AID: + radio->utf_8_flag = (radio->block[2] & 1); + radio->formatting_dir = EXTRACT_BIT(radio->block[2], + ERT_FORMAT_DIR_BIT); + if (radio->ert_carrier != app_grp_typ) { + silabs_fm_q_event(radio, SILABS_EVT_NEW_ODA); + radio->ert_carrier = app_grp_typ; + } + break; + case RT_PLUS_AID: + /*Extract 5th bit of MSB (b7b6b5b4b3b2b1b0)*/ + radio->rt_ert_flag = EXTRACT_BIT(radio->block[2], + RT_ERT_FLAG_BIT); + if (radio->rt_plus_carrier != app_grp_typ) { + silabs_fm_q_event(radio, SILABS_EVT_NEW_ODA); + radio->rt_plus_carrier = app_grp_typ; + } + break; + default: + FMDBG("Not handling the AID of %x\n", aid); + break; + } +} + +static int set_hard_mute(struct silabs_fm_device *radio, bool val) +{ + int retval = 0; + + if (val == true) { + retval = set_property(radio, RX_HARD_MUTE_PROP, HARD_MUTE_MASK); + + if (retval < 0) + FMDERR("%s: set_hard_mute failed with error %d\n", + __func__, retval); + } else { + retval = set_property(radio, RX_HARD_MUTE_PROP, 0); + + if (retval < 0) + FMDERR("%s: set_hard_mute failed with error %d\n", + __func__, retval); + } + + return retval; +} + +static int set_mute_mode(struct silabs_fm_device *radio, u16 val) +{ + int retval = 0; + + retval = set_property(radio, RX_HARD_MUTE_PROP, val); + + if (retval < 0) + FMDERR("%s: set_mute_mode failed with error %d\n", + __func__, retval); + return retval; +} +static int get_rssi(struct silabs_fm_device *radio, u8 *prssi) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip.*/ + radio->cmd = FM_RSQ_STATUS_CMD; + radio->write_buf[0] = FM_RSQ_STATUS_CMD; + radio->write_buf[1] = 1; + + retval = send_cmd(radio, RSQ_STATUS_CMD_LEN); + + if (retval < 0) + FMDERR("%s: get_rsq_status failed with error %d\n", + __func__, retval); + + FMDBG("%s: rssi is %d\n", __func__, radio->read_buf[4]); + *prssi = radio->read_buf[4]; + mutex_unlock(&radio->lock); + + return retval; +} + +static bool is_valid_freq(struct silabs_fm_device *radio, u32 freq) +{ + u32 band_low_limit = radio->recv_conf.band_low_limit * TUNE_STEP_SIZE; + u32 band_high_limit = radio->recv_conf.band_high_limit * TUNE_STEP_SIZE; + u8 spacing = 0; + + if (radio->recv_conf.ch_spacing == 0) + spacing = CH_SPACING_200; + else if (radio->recv_conf.ch_spacing == 1) + spacing = CH_SPACING_100; + else if (radio->recv_conf.ch_spacing == 2) + spacing = CH_SPACING_50; + else + return false; + + if ((freq >= band_low_limit) && + (freq <= band_high_limit) && + ((freq - band_low_limit) % spacing == 0)) + return true; + + return false; +} + +static bool is_new_freq(struct silabs_fm_device *radio, u32 freq) +{ + u8 i = 0; + + for (i = 0; i < radio->af_info2.size; i++) { + if (freq == radio->af_info2.af_list[i]) + return false; + } + + return true; +} + +static bool is_different_af_list(struct silabs_fm_device *radio) +{ + u8 i = 0, j = 0; + u32 freq; + + if (radio->af_info1.orig_freq_khz != radio->af_info2.orig_freq_khz) + return true; + + /* freq is same, check if the AFs are same. */ + for (i = 0; i < radio->af_info1.size; i++) { + freq = radio->af_info1.af_list[i]; + for (j = 0; j < radio->af_info2.size; j++) { + if (freq == radio->af_info2.af_list[j]) + break; + } + + /* freq is not there in list2 i.e list1, list2 are different.*/ + if (j == radio->af_info2.size) + return true; + } + + return false; +} + +static void reset_af_info(struct silabs_fm_device *radio) +{ + radio->af_info1.inval_freq_cnt = 0; + radio->af_info1.cnt = 0; + radio->af_info1.index = 0; + radio->af_info1.size = 0; + radio->af_info1.orig_freq_khz = 0; + memset(radio->af_info1.af_list, 0, sizeof(radio->af_info1.af_list)); + + radio->af_info2.inval_freq_cnt = 0; + radio->af_info2.cnt = 0; + radio->af_info2.index = 0; + radio->af_info2.size = 0; + radio->af_info2.orig_freq_khz = 0; + memset(radio->af_info2.af_list, 0, sizeof(radio->af_info2.af_list)); +} + +static void update_af_list(struct silabs_fm_device *radio) +{ + bool retval; + u8 i = 0; + u8 af_data = radio->block[2] >> 8; + u32 af_freq_khz; + + struct kfifo *buff; + struct af_list_ev ev; + spinlock_t lock = radio->buf_lock[SILABS_FM_BUF_AF_LIST]; + + for (; i < NO_OF_AF_IN_GRP; i++, af_data = radio->block[2] & 0xFF) { + + if (af_data >= MIN_AF_CNT_CODE && af_data <= MAX_AF_CNT_CODE) { + + FMDBG("%s: resetting af info, freq %u, pi %u\n", + __func__, radio->tuned_freq_khz, radio->pi); + radio->af_info2.inval_freq_cnt = 0; + radio->af_info2.cnt = 0; + radio->af_info2.orig_freq_khz = 0; + + /* AF count. */ + radio->af_info2.cnt = af_data - NO_AF_CNT_CODE; + radio->af_info2.orig_freq_khz = radio->tuned_freq_khz; + radio->af_info2.pi = radio->pi; + + FMDBG("%s: current freq is %u, AF cnt is %u\n", + __func__, radio->tuned_freq_khz, radio->af_info2.cnt); + + } else if (af_data >= MIN_AF_FREQ_CODE && + af_data <= MAX_AF_FREQ_CODE && + radio->af_info2.orig_freq_khz != 0 && + radio->af_info2.size < MAX_NO_OF_AF) { + + af_freq_khz = SCALE_AF_CODE_TO_FREQ_KHZ(af_data); + retval = is_valid_freq(radio, af_freq_khz); + if (retval == false) { + FMDBG("%s: Invalid AF\n", __func__); + radio->af_info2.inval_freq_cnt++; + continue; + } + + retval = is_new_freq(radio, af_freq_khz); + if (retval == false) { + FMDBG("%s: Duplicate AF\n", __func__); + radio->af_info2.inval_freq_cnt++; + continue; + } + + /* update the AF list */ + radio->af_info2.af_list[radio->af_info2.size++] = + af_freq_khz; + FMDBG("%s: AF is %u\n", __func__, af_freq_khz); + if ((radio->af_info2.size + + radio->af_info2.inval_freq_cnt == + radio->af_info2.cnt) && + is_different_af_list(radio)) { + + /* Copy the list to af_info1. */ + radio->af_info1.cnt = radio->af_info2.cnt; + radio->af_info1.size = radio->af_info2.size; + radio->af_info1.pi = radio->af_info2.pi; + radio->af_info1.orig_freq_khz = + radio->af_info2.orig_freq_khz; + memset(radio->af_info1.af_list, + 0, + sizeof(radio->af_info1.af_list)); + + memcpy(radio->af_info1.af_list, + radio->af_info2.af_list, + sizeof(radio->af_info2.af_list)); + + /* AF list changed, post it to user space */ + memset(&ev, 0, sizeof(struct af_list_ev)); + + ev.tune_freq_khz = + radio->af_info1.orig_freq_khz; + ev.pi_code = radio->pi; + ev.af_size = radio->af_info1.size; + + memcpy(&ev.af_list[0], + radio->af_info1.af_list, + GET_AF_LIST_LEN(ev.af_size)); + + buff = &radio->data_buf[SILABS_FM_BUF_AF_LIST]; + kfifo_in_locked(buff, + (u8 *)&ev, + GET_AF_EVT_LEN(ev.af_size), + &lock); + + FMDBG("%s: posting AF list evt, curr freq %u\n", + __func__, ev.tune_freq_khz); + + silabs_fm_q_event(radio, + SILABS_EVT_NEW_AF_LIST); + } + } + } +} + +static void silabs_af_tune(struct work_struct *work) +{ + struct silabs_fm_device *radio; + int retval = 0, i = 0; + u8 rssi = 0; + u32 freq = 0; + + radio = container_of(work, struct silabs_fm_device, work_af.work); + + if (radio->af_info2.size == 0) { + FMDBG("%s: Empty AF list\n", __func__); + radio->is_af_tune_in_progress = false; + return; + } + radio->af_avg_th = 0; + for (i = 0; i < radio->af_wait_timer; i++) { + retval = get_rssi(radio, &rssi); + if (retval < 0) + FMDERR("%s: getting rssi failed\n", __func__); + radio->af_avg_th += rssi; + msleep(1000); + } + radio->af_avg_th = radio->af_avg_th/radio->af_wait_timer; + if (radio->af_avg_th >= radio->af_rssi_th) { + FMDBG("Not required to do Af jump\n"); + return; + } + + + /* Disable all other interrupts except STC, RDS */ + retval = configure_interrupts(radio, ENABLE_STC_RDS_INTERRUPTS); + + /* Mute until AF tuning finishes */ + retval = set_hard_mute(radio, true); + + while (1) { + if (radio->mode != FM_RECV) { + FMDERR("%s: Drv is not in proper state\n", __func__); + goto end; + } + + if (radio->seek_tune_status != NO_SEEK_TUNE_PENDING) { + FMDBG("%s: manual tune, search issued\n", __func__); + break; + } + + if (radio->is_af_jump_enabled != true) { + FMDBG("%s: AF jump is disabled\n", __func__); + break; + } + + /* If no more AFs left, tune to original frequency and break */ + if (radio->af_info2.index >= radio->af_info2.size) { + FMDBG("%s: No more AFs, tuning to original freq %u\n", + __func__, radio->af_info2.orig_freq_khz); + + freq = radio->af_info2.orig_freq_khz; + + retval = tune(radio, freq); + if (retval < 0) { + FMDERR("%s: tune failed, error %d\n", + __func__, retval); + goto err_tune_fail; + } + + /* wait for tune to finish */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) { + FMDERR("%s: didn't receive STC for tune\n", + __func__); + /* FM is not correct state */ + continue; + } else + FMDBG("%s: received STC for tune\n", __func__); + + goto err_tune_fail; + } + + freq = radio->af_info2.af_list[radio->af_info2.index++]; + + FMDBG("%s: tuning to freq %u\n", __func__, freq); + + retval = tune(radio, freq); + if (retval < 0) { + FMDERR("%s: tune failed, error %d\n", + __func__, retval); + goto err_tune_fail; + } + + /* wait for tune to finish */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) { + FMDERR("%s: didn't receive STC for tune\n", + __func__); + /* FM is not correct state */ + continue; + } else + FMDBG("%s: received STC for tune\n", __func__); + + retval = get_rssi(radio, &rssi); + if (retval < 0) { + FMDERR("%s: getting rssi failed\n", __func__); + goto err_tune_fail; + } + + if (rssi >= radio->af_rssi_th) { + u8 j = 0; + /* clear stale RDS interrupt */ + get_rds_status(radio); + for (; j < AF_PI_WAIT_TIME && radio->pi == 0; j++) { + /* Wait for PI to be received. */ + msleep(100); + FMDBG("%s: sleeping for 100ms for PI\n", + __func__); + } + + if (radio->pi == 0 || radio->pi != radio->af_info2.pi) { + FMDBG("%s: pi %d, af freq pi %d not equal\n", + __func__, radio->pi, radio->af_info2.pi); + continue; + } + + FMDBG("%s: found AF freq(%u) >= AF th with pi %d\n", + __func__, freq, radio->pi); + /* Notify FM UI about the new freq */ + FMDBG("%s: posting TUNE_SUCC event\n", __func__); + silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC); + + break; + } + FMDBG("%s: rssi: %u, af_rssi_th: %u not eq contnuing\n", + __func__, rssi, radio->af_rssi_th); + } + +err_tune_fail: + /* + * At this point, we are tuned to either original freq or AF with >= + * AF rssi threshold + */ + if (freq != radio->af_info2.orig_freq_khz) { + FMDBG("tuned freq different than original,reset af info\n"); + reset_af_info(radio); + } + + radio->is_af_tune_in_progress = false; + + /* Clear the stale RDS int bit. */ + get_rds_status(radio); + retval = configure_interrupts(radio, ENABLE_STC_RDS_INTERRUPTS); + + /* Clear the stale RSQ int bit. */ + get_rssi(radio, &rssi); + retval = configure_interrupts(radio, ENABLE_RSQ_INTERRUPTS); + +end: + /* Unmute */ + retval = set_hard_mute(radio, false); +} + +/* When RDS interrupt is received, read and process RDS data. */ +static void rds_handler(struct work_struct *worker) +{ + struct silabs_fm_device *radio; + u8 rt_blks[NO_OF_RDS_BLKS]; + u8 grp_type, addr, ab_flg; + + radio = container_of(worker, struct silabs_fm_device, rds_worker); + + if (!radio) { + FMDERR("%s:radio is null\n", __func__); + return; + } + + FMDBG("Entered rds_handler\n"); + + get_rds_status(radio); + + pi_handler(radio, radio->block[0]); + + grp_type = radio->block[1] >> OFFSET_OF_GRP_TYP; + + FMDBG("grp_type = %d\n", grp_type); + + if (grp_type & 0x01) + pi_handler(radio, radio->block[2]); + + pty_handler(radio, (radio->block[1] >> OFFSET_OF_PTY) & PTY_MASK); + + switch (grp_type) { + case RDS_TYPE_0A: + update_af_list(radio); + /* fall through */ + case RDS_TYPE_0B: + addr = (radio->block[1] & PS_MASK) * NO_OF_CHARS_IN_EACH_ADD; + FMDBG("RDS is PS\n"); + update_ps(radio, addr+0, radio->block[3] >> 8); + update_ps(radio, addr+1, radio->block[3] & 0xff); + break; + case RDS_TYPE_2A: + FMDBG("RDS is RT 2A group\n"); + rt_blks[0] = (u8)(radio->block[2] >> 8); + rt_blks[1] = (u8)(radio->block[2] & 0xFF); + rt_blks[2] = (u8)(radio->block[3] >> 8); + rt_blks[3] = (u8)(radio->block[3] & 0xFF); + addr = (radio->block[1] & 0xf) * 4; + ab_flg = (radio->block[1] & 0x0010) >> 4; + rt_handler(radio, ab_flg, CNT_FOR_2A_GRP_RT, addr, rt_blks); + break; + case RDS_TYPE_2B: + FMDBG("RDS is RT 2B group\n"); + rt_blks[0] = (u8)(radio->block[3] >> 8); + rt_blks[1] = (u8)(radio->block[3] & 0xFF); + rt_blks[2] = 0; + rt_blks[3] = 0; + addr = (radio->block[1] & 0xf) * 2; + ab_flg = (radio->block[1] & 0x0010) >> 4; + radio->rt_tmp0[MAX_LEN_2B_GRP_RT] = END_OF_RT; + radio->rt_tmp1[MAX_LEN_2B_GRP_RT] = END_OF_RT; + radio->rt_cnt[MAX_LEN_2B_GRP_RT] = RT_VALIDATE_LIMIT; + rt_handler(radio, ab_flg, CNT_FOR_2B_GRP_RT, addr, rt_blks); + break; + case RDS_TYPE_3A: + FMDBG("RDS is 3A group\n"); + silabs_raw_rds_handler(radio); + break; + default: + FMDERR("Not handling the group type %d\n", grp_type); + break; + } + FMDBG("rt_plus_carrier = %x\n", radio->rt_plus_carrier); + FMDBG("ert_carrier = %x\n", radio->ert_carrier); + if (radio->rt_plus_carrier && (grp_type == radio->rt_plus_carrier)) + silabs_rt_plus(radio); + else if (radio->ert_carrier && (grp_type == radio->ert_carrier)) + silabs_buff_ert(radio); +} + +/* to enable, disable interrupts. */ +static int configure_interrupts(struct silabs_fm_device *radio, u8 val) +{ + int retval = 0; + u16 prop_val = 0; + + switch (val) { + case DISABLE_ALL_INTERRUPTS: + prop_val = 0; + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error disabling interrupts\n", __func__); + break; + case ENABLE_STC_RDS_INTERRUPTS: + /* enable STC and RDS interrupts. */ + prop_val = RDS_INT_BIT_MASK | STC_INT_BIT_MASK; + + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error enabling STC, RDS interrupts\n", + __func__); + break; + case ENABLE_STC_INTERRUPTS: + /* enable STC interrupts only. */ + prop_val = STC_INT_BIT_MASK; + + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error enabling STC interrupts\n", __func__); + break; + case ENABLE_RDS_INTERRUPTS: + /* enable RDS interrupts. */ + prop_val = RDS_INT_BIT_MASK | STC_INT_BIT_MASK; + if (radio->is_af_jump_enabled) + prop_val |= RSQ_INT_BIT_MASK; + + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error enabling RDS interrupts\n", + __func__); + break; + case DISABLE_RDS_INTERRUPTS: + /* disable RDS interrupts. */ + prop_val = STC_INT_BIT_MASK; + if (radio->is_af_jump_enabled) + prop_val |= RSQ_INT_BIT_MASK; + + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error disabling RDS interrupts\n", + __func__); + break; + case ENABLE_RSQ_INTERRUPTS: + /* enable RSQ interrupts. */ + prop_val = RSQ_INT_BIT_MASK | STC_INT_BIT_MASK; + if (radio->lp_mode != true) + prop_val |= RDS_INT_BIT_MASK; + + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error enabling RSQ interrupts\n", + __func__); + break; + case DISABLE_RSQ_INTERRUPTS: + /* disable RSQ interrupts. */ + prop_val = STC_INT_BIT_MASK; + if (radio->lp_mode != true) + prop_val |= RDS_INT_BIT_MASK; + + retval = set_property(radio, GPO_IEN_PROP, prop_val); + if (retval < 0) + FMDERR("%s: error disabling RSQ interrupts\n", + __func__); + break; + default: + FMDERR("%s: invalid value %u\n", __func__, val); + retval = -EINVAL; + break; + } + + return retval; +} + +static int get_int_status(struct silabs_fm_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip.*/ + radio->cmd = GET_INT_STATUS_CMD; + radio->write_buf[0] = GET_INT_STATUS_CMD; + + retval = send_cmd(radio, GET_INT_STATUS_CMD_LEN); + + if (retval < 0) + FMDERR("%s: get_int_status failed with error %d\n", + __func__, retval); + + mutex_unlock(&radio->lock); + + return retval; +} + +static void reset_rds(struct silabs_fm_device *radio) +{ + radio->pi = 0; + /* reset PS bufferes */ + memset(radio->ps_display, 0, sizeof(radio->ps_display)); + memset(radio->ps_tmp0, 0, sizeof(radio->ps_tmp0)); + memset(radio->ps_cnt, 0, sizeof(radio->ps_cnt)); + + /* reset RT buffers */ + memset(radio->rt_display, 0, sizeof(radio->rt_display)); + memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0)); + memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1)); + memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt)); +} + +static int initialize_recv(struct silabs_fm_device *radio) +{ + int retval = 0; + + retval = set_property(radio, + FM_SEEK_TUNE_SNR_THRESHOLD_PROP, + DEFAULT_SNR_TH); + if (retval < 0) { + FMDERR("%s: FM_SEEK_TUNE_SNR_THRESHOLD_PROP fail error %d\n", + __func__, retval); + goto set_prop_fail; + } + + radio->sinr_th = DEFAULT_SNR_TH; + + retval = set_property(radio, + FM_SEEK_TUNE_RSSI_THRESHOLD_PROP, + DEFAULT_RSSI_TH); + if (retval < 0) { + FMDERR("%s: FM_SEEK_TUNE_RSSI_THRESHOLD_PROP fail error %d\n", + __func__, retval); + goto set_prop_fail; + } + + radio->rssi_th = DEFAULT_RSSI_TH; + + retval = set_property(radio, + FM_RSQ_RSSI_LO_THRESHOLD_PROP, + DEFAULT_AF_RSSI_LOW_TH); + if (retval < 0) { + FMDERR("%s: FM_RSQ_RSSI_LO_THRESHOLD_PROP fail error %d\n", + __func__, retval); + goto set_prop_fail; + } + + radio->af_rssi_th = DEFAULT_AF_RSSI_LOW_TH; + + retval = set_property(radio, + FM_RSQ_INT_SOURCE_PROP, + RSSI_LOW_TH_INT_BIT_MASK); + if (retval < 0) { + FMDERR("%s: FM_RSQ_INT_SOURCE_PROP fail error %d\n", + __func__, retval); + goto set_prop_fail; + } + + retval = set_property(radio, + FM_RDS_INT_FIFO_COUNT_PROP, + FIFO_CNT_16); + if (retval < 0) { + FMDERR("%s: FM_RDS_INT_FIFO_COUNT_PROP fail error %d\n", + __func__, retval); + goto set_prop_fail; + } + + radio->rds_fifo_cnt = FIFO_CNT_16; + +set_prop_fail: + return retval; + +} + +static void init_ssr(struct silabs_fm_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + /* + * Configure status gpio to low in active state. When chip is reset for + * some reason, status gpio becomes high since pull-up resistor is + * installed. No need to return error even if it fails, since normal + * FM functionality can still work fine. + */ + + radio->cmd = GPIO_CTL_CMD; + radio->write_buf[0] = GPIO_CTL_CMD; + radio->write_buf[1] = GPIO1_OUTPUT_ENABLE_MASK; + + retval = send_cmd(radio, GPIO_CTL_CMD_LEN); + + if (retval < 0) { + FMDERR("%s: setting Silabs gpio1 as op to chip fail, err %d\n", + __func__, retval); + goto end; + } + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip.*/ + radio->cmd = GPIO_SET_CMD; + radio->write_buf[0] = GPIO_SET_CMD; + radio->write_buf[1] = GPIO_OUTPUT_LOW_MASK; + + retval = send_cmd(radio, GPIO_SET_CMD_LEN); + + if (retval < 0) + FMDERR("%s: setting gpios to low failed, error %d\n", + __func__, retval); + +end: + mutex_unlock(&radio->lock); +} + +static int enable(struct silabs_fm_device *radio) +{ + int retval = 0; + + retval = read_cts_bit(radio); + + if (retval < 0) + return retval; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip.*/ + radio->cmd = POWER_UP_CMD; + radio->write_buf[0] = POWER_UP_CMD; + radio->write_buf[1] = ENABLE_GPO2_INT_MASK; + + radio->write_buf[2] = AUDIO_OPMODE_DIGITAL; + + retval = send_cmd(radio, POWER_UP_CMD_LEN); + + if (retval < 0) { + FMDERR("%s: enable failed with error %d\n", __func__, retval); + mutex_unlock(&radio->lock); + goto send_cmd_fail; + } + + mutex_unlock(&radio->lock); + + /* enable interrupts */ + retval = configure_interrupts(radio, ENABLE_STC_RDS_INTERRUPTS); + if (retval < 0) + FMDERR("In %s, configure_interrupts failed with error %d\n", + __func__, retval); + + /* initialize with default configuration */ + retval = initialize_recv(radio); + reset_rds(radio); /* Clear the existing RDS data */ + init_ssr(radio); + if (retval >= 0) { + if (radio->mode == FM_RECV_TURNING_ON) { + FMDBG("In %s, posting SILABS_EVT_RADIO_READY event\n", + __func__); + silabs_fm_q_event(radio, SILABS_EVT_RADIO_READY); + radio->mode = FM_RECV; + } + } +send_cmd_fail: + return retval; + +} + +static int disable(struct silabs_fm_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = POWER_DOWN_CMD; + radio->write_buf[0] = POWER_DOWN_CMD; + + retval = send_cmd(radio, POWER_DOWN_CMD_LEN); + if (retval < 0) + FMDERR("%s: disable failed with error %d\n", + __func__, retval); + + mutex_unlock(&radio->lock); + + if (radio->mode == FM_TURNING_OFF || radio->mode == FM_RECV) { + FMDBG("%s: posting SILABS_EVT_RADIO_DISABLED event\n", + __func__); + silabs_fm_q_event(radio, SILABS_EVT_RADIO_DISABLED); + radio->mode = FM_OFF; + } + + return retval; +} + +static int set_chan_spacing(struct silabs_fm_device *radio, u16 spacing) +{ + int retval = 0; + u16 prop_val = 0; + + if (spacing == 0) + prop_val = FM_RX_SPACE_200KHZ; + else if (spacing == 1) + prop_val = FM_RX_SPACE_100KHZ; + else if (spacing == 2) + prop_val = FM_RX_SPACE_50KHZ; + + retval = set_property(radio, FM_SEEK_FREQ_SPACING_PROP, prop_val); + if (retval < 0) + FMDERR("In %s, error setting channel spacing\n", __func__); + else + radio->recv_conf.ch_spacing = spacing; + + return retval; + +} + +static int set_emphasis(struct silabs_fm_device *radio, u16 emp) +{ + int retval = 0; + u16 prop_val = 0; + + if (emp == 0) + prop_val = FM_RX_EMP75; + else if (emp == 1) + prop_val = FM_RX_EMP50; + + retval = set_property(radio, FM_DEEMPHASIS_PROP, prop_val); + if (retval < 0) + FMDERR("In %s, error setting emphasis\n", __func__); + else + radio->recv_conf.emphasis = emp; + + return retval; + +} + +static int tune(struct silabs_fm_device *radio, u32 freq_khz) +{ + int retval = 0; + u16 freq_16bit = (u16)(freq_khz/TUNE_STEP_SIZE); + + FMDBG("In %s, freq is %d\n", __func__, freq_khz); + + /* + * when we are tuning for the first time, we must set digital audio + * properties. + */ + if (radio->first_tune) { + /* I2S mode, rising edge */ + retval = set_property(radio, DIGITAL_OUTPUT_FORMAT_PROP, 0); + if (retval < 0) { + FMDERR("%s: set output format prop failed, error %d\n", + __func__, retval); + goto set_prop_fail; + } + + /* 48khz sample rate */ + retval = set_property(radio, + DIGITAL_OUTPUT_SAMPLE_RATE_PROP, + SAMPLE_RATE_48_KHZ); + if (retval < 0) { + FMDERR("%s: set sample rate prop failed, error %d\n", + __func__, retval); + goto set_prop_fail; + } + radio->first_tune = false; + } + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip.*/ + radio->cmd = FM_TUNE_FREQ_CMD; + + radio->write_buf[0] = FM_TUNE_FREQ_CMD; + /* reserved */ + radio->write_buf[1] = 0; + /* freq high byte */ + radio->write_buf[2] = HIGH_BYTE_16BIT(freq_16bit); + /* freq low byte */ + radio->write_buf[3] = LOW_BYTE_16BIT(freq_16bit); + radio->write_buf[4] = 0; + + FMDBG("In %s, radio->write_buf[2] %x, radio->write_buf[3]%x\n", + __func__, radio->write_buf[2], radio->write_buf[3]); + + retval = send_cmd(radio, TUNE_FREQ_CMD_LEN); + if (retval < 0) + FMDERR("In %s, tune failed with error %d\n", __func__, retval); + + mutex_unlock(&radio->lock); + +set_prop_fail: + return retval; +} + +static int silabs_seek(struct silabs_fm_device *radio, int dir, int wrap) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = FM_SEEK_START_CMD; + + radio->write_buf[0] = FM_SEEK_START_CMD; + if (wrap) + radio->write_buf[1] = SEEK_WRAP_MASK; + + if (dir == SRCH_DIR_UP) + radio->write_buf[1] |= SEEK_UP_MASK; + + retval = send_cmd(radio, SEEK_CMD_LEN); + if (retval < 0) + FMDERR("In %s, seek failed with error %d\n", __func__, retval); + + mutex_unlock(&radio->lock); + return retval; +} + + +static int cancel_seek(struct silabs_fm_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = FM_TUNE_STATUS_CMD; + + radio->write_buf[0] = FM_TUNE_STATUS_CMD; + radio->write_buf[1] = CANCEL_SEEK_MASK; + + retval = send_cmd(radio, TUNE_STATUS_CMD_LEN); + if (retval < 0) + FMDERR("%s: cancel_seek failed, error %d\n", __func__, retval); + + mutex_unlock(&radio->lock); + radio->is_search_cancelled = true; + + return retval; + +} + +static void silabs_fm_q_event(struct silabs_fm_device *radio, + enum silabs_evt_t event) +{ + + struct kfifo *data_b; + unsigned char evt = event; + + data_b = &radio->data_buf[SILABS_FM_BUF_EVENTS]; + + FMDBG("updating event_q with event %x\n", event); + if (kfifo_in_locked(data_b, + &evt, + 1, + &radio->buf_lock[SILABS_FM_BUF_EVENTS])) + wake_up_interruptible(&radio->event_queue); +} + +static int clear_stc_int(struct silabs_fm_device *radio) +{ + int retval = 0; + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = FM_TUNE_STATUS_CMD; + + radio->write_buf[0] = FM_TUNE_STATUS_CMD; + radio->write_buf[1] = INTACK_MASK; + + retval = send_cmd(radio, TUNE_STATUS_CMD_LEN); + if (retval < 0) + FMDERR("%s: clear_stc_int fail, error %d\n", __func__, retval); + + mutex_unlock(&radio->lock); + + return retval; +} + +static void silabs_interrupts_handler(struct silabs_fm_device *radio) +{ + int retval = 0; + u8 rssi = 0; + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return; + } + + FMDBG("%s: ISR fired for cmd %x, reading status bytes\n", + __func__, radio->cmd); + + /* Get int status to know which interrupt is this(STC/RDS/etc) */ + retval = get_int_status(radio); + + if (retval < 0) { + FMDERR("%s: failure reading the resp from soc with error %d\n", + __func__, retval); + return; + } + FMDBG("%s: successfully read the resp from soc, status byte is %x\n", + __func__, radio->read_buf[0]); + + + if (radio->read_buf[0] & STC_INT_BIT_MASK) { + FMDBG("%s: STC bit set for cmd %x\n", __func__, radio->cmd); + if (radio->seek_tune_status == TUNE_PENDING) { + FMDBG("In %s, posting SILABS_EVT_TUNE_SUCC event\n", + __func__); + silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC); + radio->seek_tune_status = NO_SEEK_TUNE_PENDING; + radio->is_af_tune_in_progress = false; + } else if (radio->seek_tune_status == SEEK_PENDING) { + FMDBG("%s: posting SILABS_EVT_SEEK_COMPLETE event\n", + __func__); + silabs_fm_q_event(radio, SILABS_EVT_SEEK_COMPLETE); + /* post tune comp evt since seek results in a tune.*/ + FMDBG("%s: posting SILABS_EVT_TUNE_SUCC\n", + __func__); + silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC); + radio->seek_tune_status = NO_SEEK_TUNE_PENDING; + + } else if (radio->seek_tune_status == SCAN_PENDING) { + /* + * when scan is pending and STC int is set, signal + * so that scan can proceed + */ + FMDBG("In %s, signalling scan thread\n", __func__); + complete(&radio->sync_req_done); + } else if (radio->is_af_tune_in_progress == true) { + /* + * when AF tune is going on and STC int is set, signal + * so that AF tune can proceed. + */ + FMDBG("In %s, signalling AF tune thread\n", __func__); + complete(&radio->sync_req_done); + } + /* clear the STC interrupt. */ + clear_stc_int(radio); + reset_rds(radio); /* Clear the existing RDS data */ + return; + } + + if (radio->read_buf[0] & RSQ_INT_BIT_MASK) { + FMDBG("RSQ interrupt received, clearing the RSQ int bit\n"); + + /* clear RSQ interrupt bits until AF tune is complete. */ + (void)get_rssi(radio, &rssi); + /* Don't process RSQ until AF tune is complete. */ + if (radio->is_af_tune_in_progress == true) + return; + + if (radio->is_af_jump_enabled && + radio->af_info2.size != 0 && + rssi <= radio->af_rssi_th) { + + radio->is_af_tune_in_progress = true; + FMDBG("%s: Queuing to AF work Q, freq %u, rssi %u\n", + __func__, radio->tuned_freq_khz, rssi); + queue_delayed_work(radio->wqueue_af, &radio->work_af, + msecs_to_jiffies(SILABS_DELAY_MSEC)); + } + return; + } + + if (radio->read_buf[0] & RDS_INT_BIT_MASK) { + FMDBG("RDS interrupt received\n"); + /* Don't process RDS until AF tune is complete. */ + if (radio->is_af_tune_in_progress == true) { + /* get PI only */ + get_rds_status(radio); + pi_handler(radio, radio->block[0]); + return; + } + schedule_work(&radio->rds_worker); + return; + } +} + +static void read_int_stat(struct work_struct *work) +{ + struct silabs_fm_device *radio; + + radio = container_of(work, struct silabs_fm_device, work.work); + + silabs_interrupts_handler(radio); +} + +static void silabs_fm_disable_irq(struct silabs_fm_device *radio) +{ + int irq; + + irq = radio->irq; + disable_irq_wake(irq); + free_irq(irq, radio); + + irq = radio->status_irq; + disable_irq_wake(irq); + free_irq(irq, radio); + + cancel_work_sync(&radio->rds_worker); + flush_workqueue(radio->wqueue_rds); + cancel_delayed_work_sync(&radio->work); + flush_workqueue(radio->wqueue); + cancel_delayed_work_sync(&radio->work_scan); + flush_workqueue(radio->wqueue_scan); + cancel_delayed_work_sync(&radio->work_af); + flush_workqueue(radio->wqueue_af); +} + +static irqreturn_t silabs_fm_isr(int irq, void *dev_id) +{ + struct silabs_fm_device *radio = dev_id; + /* + * The call to queue_delayed_work ensures that a minimum delay + * (in jiffies) passes before the work is actually executed. The return + * value from the function is nonzero if the work_struct was actually + * added to queue (otherwise, it may have already been there and will + * not be added a second time). + */ + + queue_delayed_work(radio->wqueue, &radio->work, + msecs_to_jiffies(SILABS_DELAY_MSEC)); + + return IRQ_HANDLED; +} + +static irqreturn_t silabs_fm_status_isr(int irq, void *dev_id) +{ + struct silabs_fm_device *radio = dev_id; + + if (radio->mode == FM_TURNING_OFF || radio->mode == FM_RECV) { + FMDERR("%s: chip in bad state, posting DISABLED event\n", + __func__); + silabs_fm_q_event(radio, SILABS_EVT_RADIO_DISABLED); + radio->mode = FM_OFF; + } + + return IRQ_HANDLED; +} + +static int silabs_fm_request_irq(struct silabs_fm_device *radio) +{ + int retval; + int irq; + + irq = radio->irq; + + /* + * Use request_any_context_irq, So that it might work for nested or + * nested interrupts. + */ + retval = request_any_context_irq(irq, silabs_fm_isr, + IRQ_TYPE_EDGE_FALLING, "fm interrupt", radio); + if (retval < 0) { + FMDERR("Couldn't acquire FM gpio %d\n", irq); + return retval; + } + FMDBG("FM GPIO %d registered\n", irq); + + retval = enable_irq_wake(irq); + if (retval < 0) { + FMDERR("Could not enable FM interrupt\n "); + free_irq(irq, radio); + return retval; + } + + irq = radio->status_irq; + + retval = request_any_context_irq(irq, silabs_fm_status_isr, + IRQ_TYPE_EDGE_RISING, "fm status interrupt", + radio); + if (retval < 0) { + FMDERR("Couldn't acquire FM status gpio %d\n", irq); + /* Do not error out for status int. FM can work without it. */ + return 0; + } + FMDBG("FM status GPIO %d registered\n", irq); + + retval = enable_irq_wake(irq); + if (retval < 0) { + FMDERR("Could not enable FM status interrupt\n "); + free_irq(irq, radio); + /* Do not error out for status int. FM can work without it. */ + return 0; + } + + return retval; +} + +static int silabs_fm_fops_open(struct file *file) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int retval = -ENODEV; + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + INIT_DELAYED_WORK(&radio->work, read_int_stat); + INIT_DELAYED_WORK(&radio->work_scan, silabs_scan); + INIT_DELAYED_WORK(&radio->work_af, silabs_af_tune); + INIT_WORK(&radio->rds_worker, rds_handler); + + init_completion(&radio->sync_req_done); + if (!atomic_dec_and_test(&radio->users)) { + FMDBG("%s: Device already in use. Try again later", __func__); + atomic_inc(&radio->users); + return -EBUSY; + } + + /* initial gpio pin config & Power up */ + retval = silabs_fm_power_cfg(radio, TURNING_ON); + if (retval) { + FMDERR("%s: failed config gpio & pmic\n", __func__); + goto open_err_setup; + } + radio->irq = gpio_to_irq(radio->int_gpio); + + if (radio->irq < 0) { + FMDERR("%s: gpio_to_irq returned %d\n", __func__, radio->irq); + goto open_err_req_irq; + } + + FMDBG("irq number is = %d\n", radio->irq); + + if (radio->status_gpio > 0) { + radio->status_irq = gpio_to_irq(radio->status_gpio); + + if (radio->status_irq < 0) { + FMDERR("%s: gpio_to_irq returned %d for status gpio\n", + __func__, radio->irq); + goto open_err_req_irq; + } + + FMDBG("status irq number is = %d\n", radio->status_irq); + } + + /* enable irq */ + retval = silabs_fm_request_irq(radio); + if (retval < 0) { + FMDERR("%s: failed to request irq\n", __func__); + goto open_err_req_irq; + } + + radio->handle_irq = 0; + radio->first_tune = true; + return 0; + +open_err_req_irq: + silabs_fm_power_cfg(radio, TURNING_OFF); +open_err_setup: + radio->handle_irq = 1; + atomic_inc(&radio->users); + return retval; +} + +static int silabs_fm_fops_release(struct file *file) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + + if (unlikely(radio == NULL)) + return -EINVAL; + + if (radio->mode == FM_RECV) { + radio->mode = FM_OFF; + retval = disable(radio); + if (retval < 0) + FMDERR("Err on disable FM %d\n", retval); + } + + FMDBG("%s, Disabling the IRQs\n", __func__); + /* disable irq */ + silabs_fm_disable_irq(radio); + + retval = silabs_fm_power_cfg(radio, TURNING_OFF); + if (retval < 0) + FMDERR("%s: failed to configure gpios\n", __func__); + + atomic_inc(&radio->users); + + return retval; +} + +static struct v4l2_queryctrl silabs_fm_v4l2_queryctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0, + .maximum = 15, + .step = 1, + .default_value = 15, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_SRCHON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Search on/off", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_SILABS_STATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio 0ff/rx/tx/reset", + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_SILABS_REGION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio standard", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_SIGNAL_TH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Signal Threshold", + .minimum = 0x80, + .maximum = 0x7F, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_EMPHASIS, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Emphasis", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_RDS_STD, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS standard", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_SPACING, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Channel spacing", + .minimum = 0, + .maximum = 2, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_RDSON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS on/off", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_RDSGROUP_MASK, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS group mask", + .minimum = 0, + .maximum = 0xFFFFFFFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS processing", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_RDSD_BUF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS data groups to buffer", + .minimum = 1, + .maximum = 21, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_PSALL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "pass all ps strings", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_LP_MODE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Low power mode", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_SILABS_ANTENNA, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "headset/internal", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + +}; + +static int silabs_fm_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + if (unlikely(capability == NULL)) { + FMDERR("%s:capability is null", __func__); + return -EINVAL; + } + + strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, 4, "I2C"); + capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + + return 0; +} + +static int silabs_fm_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + unsigned char i; + int retval = -EINVAL; + + if (unlikely(qc == NULL)) { + FMDERR("%s:qc is null", __func__); + return -EINVAL; + } + + + for (i = 0; i < ARRAY_SIZE(silabs_fm_v4l2_queryctrl); i++) { + if (qc->id && qc->id == silabs_fm_v4l2_queryctrl[i].id) { + memcpy(qc, &(silabs_fm_v4l2_queryctrl[i]), + sizeof(*qc)); + retval = 0; + break; + } + } + if (retval < 0) + FMDERR("query conv4ltrol failed with %d\n", retval); + + return retval; +} + +static int silabs_fm_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return -EINVAL; + } + + if (ctrl == NULL) { + FMDERR("%s, v4l2 ctrl is null\n", __func__); + return -EINVAL; + } + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + break; + + case V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC: + ctrl->value = 0; + retval = 0; + break; + case V4L2_CID_PRIVATE_SILABS_GET_SINR: + + mutex_lock(&radio->lock); + radio->cmd = FM_TUNE_STATUS_CMD; + + radio->write_buf[0] = FM_TUNE_STATUS_CMD; + radio->write_buf[1] = 0; + + retval = send_cmd(radio, TUNE_STATUS_CMD_LEN); + if (retval < 0) { + FMDERR("%s: FM_TUNE_STATUS_CMD failed with error %d\n", + __func__, retval); + mutex_unlock(&radio->lock); + break; + } + + /* sinr */ + ctrl->value = radio->read_buf[5]; + mutex_unlock(&radio->lock); + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_GET_SINR, val %d\n", + __func__, ctrl->value); + break; + case V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD, val %d\n", + __func__, radio->sinr_th); + + ctrl->value = radio->sinr_th; + break; + case V4L2_CID_PRIVATE_SILABS_RSSI_TH: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_RSSI_TH, val %d\n", + __func__, radio->rssi_th); + + ctrl->value = radio->rssi_th; + break; + case V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH, val %d\n", + __func__, radio->af_rssi_th); + + ctrl->value = radio->af_rssi_th; + break; + + case V4L2_CID_PRIVATE_SILABS_RDSD_BUF: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_RDSD_BUF, val %d\n", + __func__, radio->rds_fifo_cnt); + + ctrl->value = radio->rds_fifo_cnt; + break; + case V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES, val %d\n", + __func__, ctrl->value); + ctrl->value = radio->af_wait_timer; + break; + default: + retval = -EINVAL; + break; + } + + if (retval < 0) + FMDERR("get control failed with %d, id: %x\n", + retval, ctrl->id); + + return retval; +} + +static int silabs_fm_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + if (unlikely(ctrl == NULL)) { + FMDERR("%s:ctrl is null", __func__); + return -EINVAL; + } + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if ((ctrl->value >= 0) && (ctrl->value <= 3)) { + set_mute_mode(radio, ctrl->value); + } else { + FMDERR("%s: Mute mode states are out of range\n", + __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_STATE: + /* check if already on */ + if (ctrl->value == FM_RECV) { + if (is_enable_rx_possible(radio) != 0) { + FMDERR("%s: fm is not in proper state\n", + __func__); + retval = -EINVAL; + goto end; + } + radio->mode = FM_RECV_TURNING_ON; + + retval = enable(radio); + if (retval < 0) { + FMDERR("Error while enabling RECV FM %d\n", + retval); + radio->mode = FM_OFF; + goto end; + } + } else if (ctrl->value == FM_OFF) { + retval = configure_interrupts(radio, + DISABLE_ALL_INTERRUPTS); + if (retval < 0) + FMDERR("configure_interrupts failed %d\n", + retval); + flush_workqueue(radio->wqueue); + cancel_work_sync(&radio->rds_worker); + flush_workqueue(radio->wqueue_rds); + radio->mode = FM_TURNING_OFF; + retval = disable(radio); + if (retval < 0) { + FMDERR("Err on disable recv FM %d\n", retval); + radio->mode = FM_RECV; + goto end; + } + } + break; + case V4L2_CID_PRIVATE_SILABS_SPACING: + if (!is_valid_chan_spacing(ctrl->value)) { + retval = -EINVAL; + FMDERR("%s: channel spacing is not valid\n", __func__); + goto end; + } + retval = set_chan_spacing(radio, (u16)ctrl->value); + if (retval < 0) { + FMDERR("Error in setting channel spacing\n"); + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_EMPHASIS: + retval = set_emphasis(radio, (u16)ctrl->value); + if (retval < 0) { + FMDERR("Error in setting emphasis\n"); + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_ANTENNA: + if (ctrl->value == 0 || ctrl->value == 1) { + retval = set_property(radio, + FM_ANTENNA_INPUT_PROP, + ctrl->value); + if (retval < 0) + FMDERR("Setting antenna type failed\n"); + else + radio->antenna = ctrl->value; + } else { + retval = -EINVAL; + FMDERR("%s: antenna type is not valid\n", __func__); + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_SOFT_MUTE: + retval = 0; + break; + case V4L2_CID_PRIVATE_SILABS_REGION: + case V4L2_CID_PRIVATE_SILABS_SRCH_ALGORITHM: + case V4L2_CID_PRIVATE_SILABS_SET_AUDIO_PATH: + /* + * These private controls are place holders to keep the + * driver compatible with changes done in the frameworks + * which are specific to TAVARUA. + */ + retval = 0; + break; + case V4L2_CID_PRIVATE_SILABS_SRCHMODE: + if (is_valid_srch_mode(ctrl->value)) { + radio->g_search_mode = ctrl->value; + } else { + FMDERR("%s: srch mode is not valid\n", __func__); + retval = -EINVAL; + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_SCANDWELL: + if ((ctrl->value >= MIN_DWELL_TIME) && + (ctrl->value <= MAX_DWELL_TIME)) { + radio->dwell_time_sec = ctrl->value; + } else { + FMDERR("%s: scandwell period is not valid\n", __func__); + retval = -EINVAL; + } + break; + case V4L2_CID_PRIVATE_SILABS_SRCHON: + silabs_search(radio, (bool)ctrl->value); + break; + case V4L2_CID_PRIVATE_SILABS_RDS_STD: + return retval; + case V4L2_CID_PRIVATE_SILABS_RDSON: + return retval; + case V4L2_CID_PRIVATE_SILABS_RDSGROUP_MASK: + retval = set_property(radio, + FM_RDS_INT_SOURCE_PROP, + RDS_INT_BIT); + if (retval < 0) { + FMDERR("In %s, FM_RDS_INT_SOURCE_PROP failed %d\n", + __func__, retval); + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_RDSD_BUF: + if (is_valid_rds_fifo_cnt(ctrl->value)) { + retval = set_property(radio, + FM_RDS_INT_FIFO_COUNT_PROP, + ctrl->value); + if (retval < 0) { + FMDERR("%s: setting rds fifo cnt failed %d\n", + __func__, retval); + goto end; + } + + radio->rds_fifo_cnt = ctrl->value; + } else + retval = -EINVAL; + + break; + case V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC: + /* Enabled all with uncorrectable */ + retval = set_property(radio, + FM_RDS_CONFIG_PROP, + UNCORRECTABLE_RDS_EN); + if (retval < 0) { + FMDERR("In %s, FM_RDS_CONFIG_PROP failed %d\n", + __func__, retval); + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_LP_MODE: + FMDBG("In %s, V4L2_CID_PRIVATE_SILABS_LP_MODE, val is %d\n", + __func__, ctrl->value); + if (ctrl->value) { + /* disable RDS interrupts */ + retval = configure_interrupts(radio, + ENABLE_RDS_INTERRUPTS); + radio->lp_mode = true; + } else { + /* enable RDS interrupts */ + retval = configure_interrupts(radio, + DISABLE_RDS_INTERRUPTS); + radio->lp_mode = false; + } + + if (retval < 0) { + FMDERR("In %s, setting low power mode failed %d\n", + __func__, retval); + goto end; + } + break; + case V4L2_CID_PRIVATE_SILABS_AF_JUMP: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_JUMP, val is %d\n", + __func__, ctrl->value); + if (ctrl->value) + /* enable RSQ interrupts */ + retval = configure_interrupts(radio, + ENABLE_RSQ_INTERRUPTS); + else + /* disable RSQ interrupts */ + retval = configure_interrupts(radio, + DISABLE_RSQ_INTERRUPTS); + if (retval < 0) { + FMDERR("%s: setting AF jump mode failed %d\n", + __func__, retval); + goto end; + } + /* Save the AF jump state */ + radio->is_af_jump_enabled = ctrl->value; + break; + case V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD, val is %d\n", + __func__, ctrl->value); + if (is_valid_sinr(ctrl->value)) { + retval = set_property(radio, + FM_SEEK_TUNE_SNR_THRESHOLD_PROP, + ctrl->value); + if (retval < 0) { + FMDERR("%s: setting sinr th failed, error %d\n", + __func__, retval); + goto end; + } + + radio->sinr_th = ctrl->value; + + } else { + retval = -EINVAL; + FMDERR("%s: Invalid sinr\n", __func__); + } + break; + case V4L2_CID_PRIVATE_SILABS_RSSI_TH: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_RSSI_TH, val is %d\n", + __func__, ctrl->value); + if (is_valid_rssi(ctrl->value)) { + retval = set_property(radio, + FM_SEEK_TUNE_RSSI_THRESHOLD_PROP, + ctrl->value); + if (retval < 0) { + FMDERR("%s: setting rssi th failed, error %d\n", + __func__, retval); + goto end; + } + + radio->rssi_th = ctrl->value; + + } else { + retval = -EINVAL; + FMDERR("%s: Invalid sinr\n", __func__); + } + break; + case V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH, val %d\n", + __func__, ctrl->value); + if (is_valid_rssi(ctrl->value)) { + retval = set_property(radio, + FM_RSQ_RSSI_LO_THRESHOLD_PROP, + ctrl->value); + if (retval < 0) { + FMDERR("%s: setting af rssi th failed err %d\n", + __func__, retval); + goto end; + } + + radio->af_rssi_th = ctrl->value; + + } else { + retval = -EINVAL; + FMDERR("%s: Invalid sinr\n", __func__); + } + + break; + case V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES: + FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES, val %d\n", + __func__, ctrl->value); + if ((ctrl->value >= 0) || (ctrl->value <= MAX_AF_WAIT_SEC)) + radio->af_wait_timer = ctrl->value; + break; + case V4L2_CID_PRIVATE_SILABS_SRCH_CNT: + retval = 0; + break; + default: + retval = -EINVAL; + break; + } + + if (retval < 0) + FMDERR("set control failed with %d, id:%x\n", retval, ctrl->id); + +end: + return retval; +} + +static int silabs_fm_vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + u16 prop_val = 0; + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + if (unlikely(tuner == NULL)) { + FMDERR("%s:tuner is null", __func__); + return -EINVAL; + } + + if (tuner->index > 0) + return -EINVAL; + + FMDBG("In %s, setting top and bottom band limits\n", __func__); + + prop_val = (u16)((tuner->rangelow / TUNE_PARAM) / TUNE_STEP_SIZE); + FMDBG("In %s, tuner->rangelow is %d, setting bottom band to %d\n", + __func__, tuner->rangelow, prop_val); + + retval = set_property(radio, FM_SEEK_BAND_BOTTOM_PROP, prop_val); + if (retval < 0) + FMDERR("In %s, error %d setting lower limit freq\n", + __func__, retval); + else + radio->recv_conf.band_low_limit = prop_val; + + prop_val = (u16)((tuner->rangehigh / TUNE_PARAM) / TUNE_STEP_SIZE); + FMDBG("In %s, tuner->rangehigh is %d, setting top band to %d\n", + __func__, tuner->rangehigh, prop_val); + + retval = set_property(radio, FM_SEEK_BAND_TOP_PROP, prop_val); + if (retval < 0) + FMDERR("In %s, error %d setting upper limit freq\n", + __func__, retval); + else + radio->recv_conf.band_high_limit = prop_val; + + if (retval < 0) + FMDERR(": set tuner failed with %d\n", retval); + + return retval; +} + +static int silabs_fm_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int retval = 0; + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return -EINVAL; + } + if (tuner == NULL) { + FMDERR("%s, tuner is null\n", __func__); + return -EINVAL; + } + if (tuner->index > 0) { + FMDERR("Invalid Tuner Index"); + return -EINVAL; + } + + mutex_lock(&radio->lock); + + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = FM_TUNE_STATUS_CMD; + + radio->write_buf[0] = FM_TUNE_STATUS_CMD; + radio->write_buf[1] = 0; + + retval = send_cmd(radio, TUNE_STATUS_CMD_LEN); + if (retval < 0) { + FMDERR("In %s, FM_TUNE_STATUS_CMD failed with error %d\n", + __func__, retval); + mutex_unlock(&radio->lock); + goto get_prop_fail; + } + + /* rssi */ + tuner->signal = radio->read_buf[4]; + mutex_unlock(&radio->lock); + + retval = get_property(radio, + FM_SEEK_BAND_BOTTOM_PROP, + &radio->recv_conf.band_low_limit); + if (retval < 0) { + FMDERR("%s: get FM_SEEK_BAND_BOTTOM_PROP failed, error %d\n", + __func__, retval); + goto get_prop_fail; + } + + FMDBG("In %s, radio->recv_conf.band_low_limit is %d\n", + __func__, radio->recv_conf.band_low_limit); + retval = get_property(radio, + FM_SEEK_BAND_TOP_PROP, + &radio->recv_conf.band_high_limit); + if (retval < 0) { + FMDERR("In %s, get FM_SEEK_BAND_TOP_PROP failed, error %d\n", + __func__, retval); + goto get_prop_fail; + } + FMDBG("In %s, radio->recv_conf.band_high_limit is %d\n", + __func__, radio->recv_conf.band_high_limit); + + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + radio->recv_conf.band_low_limit * TUNE_STEP_SIZE * TUNE_PARAM; + tuner->rangehigh = + radio->recv_conf.band_high_limit * TUNE_STEP_SIZE * TUNE_PARAM; + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_LOW; + + tuner->audmode = 0; + tuner->afc = 0; + +get_prop_fail: + return retval; +} + +static int silabs_fm_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + u32 f; + u8 snr, rssi; + int retval = 0; + + if (unlikely(radio == NULL)) { + FMDERR(":radio is null"); + return -EINVAL; + } + + if (freq == NULL) { + FMDERR("%s, v4l2 freq is null\n", __func__); + return -EINVAL; + } + + mutex_lock(&radio->lock); + memset(radio->write_buf, 0, WRITE_REG_NUM); + + /* track command that is being sent to chip. */ + radio->cmd = FM_TUNE_STATUS_CMD; + + radio->write_buf[0] = FM_TUNE_STATUS_CMD; + radio->write_buf[1] = 0; + + retval = send_cmd(radio, TUNE_STATUS_CMD_LEN); + if (retval < 0) { + FMDERR("In %s, get station freq cmd failed with error %d\n", + __func__, retval); + mutex_unlock(&radio->lock); + goto send_cmd_fail; + } + + f = (radio->read_buf[2] << 8) + radio->read_buf[3]; + freq->frequency = f * TUNE_PARAM * TUNE_STEP_SIZE; + radio->tuned_freq_khz = f * TUNE_STEP_SIZE; + + rssi = radio->read_buf[4]; + snr = radio->read_buf[5]; + mutex_unlock(&radio->lock); + + FMDBG("In %s, freq is %d, rssi %u, snr %u\n", + __func__, f * TUNE_STEP_SIZE, rssi, snr); + +send_cmd_fail: + return retval; +} + +static int silabs_fm_vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *freq) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int retval = -1; + u32 f = 0; + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + if (unlikely(freq == NULL)) { + FMDERR("%s:freq is null", __func__); + return -EINVAL; + } + + if (freq->type != V4L2_TUNER_RADIO) + return -EINVAL; + + f = (freq->frequency)/TUNE_PARAM; + + FMDBG("Calling tune with freq %u\n", f); + + radio->seek_tune_status = TUNE_PENDING; + + retval = tune(radio, f); + + /* save the current frequency if tune is successful. */ + if (retval > 0) { + radio->tuned_freq_khz = f; + /* Clear AF list */ + reset_af_info(radio); + cancel_delayed_work_sync(&radio->work_af); + flush_workqueue(radio->wqueue_af); + } + + return retval; +} + +static int silabs_fm_vidioc_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + int dir; + int retval = 0; + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + if (unlikely(seek == NULL)) { + FMDERR("%s:seek is null", __func__); + return -EINVAL; + } + + if (seek->seek_upward) + dir = SRCH_DIR_UP; + else + dir = SRCH_DIR_DOWN; + + radio->is_search_cancelled = false; + + if (radio->g_search_mode == SEEK) { + /* seek */ + FMDBG("starting seek\n"); + + radio->seek_tune_status = SEEK_PENDING; + + retval = silabs_seek(radio, dir, WRAP_ENABLE); + + } else if ((radio->g_search_mode == SCAN) || + (radio->g_search_mode == SCAN_FOR_STRONG)) { + /* scan */ + if (radio->g_search_mode == SCAN_FOR_STRONG) { + FMDBG("starting search list\n"); + memset(&radio->srch_list, 0, + sizeof(struct silabs_srch_list_compl)); + } else { + FMDBG("starting scan\n"); + } + silabs_search(radio, START_SCAN); + + } else { + retval = -EINVAL; + FMDERR("In %s, invalid search mode %d\n", + __func__, radio->g_search_mode); + } + + if (retval > 0) { + /* Clear AF list */ + reset_af_info(radio); + cancel_delayed_work_sync(&radio->work_af); + flush_workqueue(radio->wqueue_af); + } + + return retval; +} + +static int silabs_fm_vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buffer) +{ + + struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file)); + enum silabs_buf_t buf_type = -1; + u8 buf_fifo[STD_BUF_SIZE] = {0}; + struct kfifo *data_fifo = NULL; + u8 *buf = NULL; + int len = 0, retval = -1; + + if ((radio == NULL) || (buffer == NULL)) { + FMDERR("radio/buffer is NULL\n"); + return -ENXIO; + } + buf_type = buffer->index; + buf = (u8 *)buffer->m.userptr; + len = buffer->length; + FMDBG("%s: requesting buffer %d\n", __func__, buf_type); + + if ((buf_type < SILABS_FM_BUF_MAX) && (buf_type >= 0)) { + data_fifo = &radio->data_buf[buf_type]; + if (buf_type == SILABS_FM_BUF_EVENTS) { + if (wait_event_interruptible(radio->event_queue, + kfifo_len(data_fifo)) < 0) { + return -EINTR; + } + } + } else { + FMDERR("invalid buffer type\n"); + return -EINVAL; + } + if (len <= STD_BUF_SIZE) { + buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0], + len, &radio->buf_lock[buf_type]); + } else { + FMDERR("kfifo_out_locked can not use len more than 128\n"); + return -EINVAL; + } + retval = copy_to_user((void __user *)buf, &buf_fifo[0], + buffer->bytesused); + if (retval > 0) { + FMDERR("Failed to copy %d bytes of data\n", retval); + return -EAGAIN; + } + + return retval; +} + +static int silabs_fm_pinctrl_init(struct silabs_fm_device *radio) +{ + int retval = 0; + + radio->fm_pinctrl = devm_pinctrl_get(&radio->client->dev); + if (IS_ERR_OR_NULL(radio->fm_pinctrl)) { + FMDERR("%s: target does not use pinctrl\n", __func__); + retval = PTR_ERR(radio->fm_pinctrl); + return retval; + } + + radio->gpio_state_active = + pinctrl_lookup_state(radio->fm_pinctrl, + "pmx_fm_active"); + if (IS_ERR_OR_NULL(radio->gpio_state_active)) { + FMDERR("%s: cannot get FM active state\n", __func__); + retval = PTR_ERR(radio->gpio_state_active); + goto err_active_state; + } + + radio->gpio_state_suspend = + pinctrl_lookup_state(radio->fm_pinctrl, + "pmx_fm_suspend"); + if (IS_ERR_OR_NULL(radio->gpio_state_suspend)) { + FMDERR("%s: cannot get FM suspend state\n", __func__); + retval = PTR_ERR(radio->gpio_state_suspend); + goto err_suspend_state; + } + + return retval; + +err_suspend_state: + radio->gpio_state_suspend = NULL; + +err_active_state: + radio->gpio_state_active = NULL; + + return retval; +} + +static int silabs_parse_dt(struct device *dev, + struct silabs_fm_device *radio) +{ + int rc = 0; + struct device_node *np = dev->of_node; + + radio->reset_gpio = of_get_named_gpio(np, "silabs,reset-gpio", 0); + if (radio->reset_gpio < 0) { + FMDERR("silabs-reset-gpio not provided in device tree"); + return radio->reset_gpio; + } + + rc = gpio_request(radio->reset_gpio, "fm_rst_gpio_n"); + if (rc) { + FMDERR("unable to request gpio %d (%d)\n", + radio->reset_gpio, rc); + return rc; + } + + radio->int_gpio = of_get_named_gpio(np, "silabs,int-gpio", 0); + if (radio->int_gpio < 0) { + FMDERR("silabs-int-gpio not provided in device tree"); + rc = radio->int_gpio; + goto err_int_gpio; + } + + rc = gpio_request(radio->int_gpio, "silabs_fm_int_n"); + if (rc) { + FMDERR("unable to request gpio %d (%d)\n", + radio->int_gpio, rc); + goto err_int_gpio; + } + + radio->status_gpio = of_get_named_gpio(np, "silabs,status-gpio", 0); + if (radio->status_gpio < 0) { + FMDERR("silabs-status-gpio not provided in device tree"); + } else { + rc = gpio_request(radio->status_gpio, "silabs_fm_stat_n"); + if (rc) { + FMDERR("unable to request status gpio %d (%d)\n", + radio->status_gpio, rc); + goto err_status_gpio; + } + } + return rc; + +err_status_gpio: + gpio_free(radio->int_gpio); +err_int_gpio: + gpio_free(radio->reset_gpio); + + return rc; +} + +static int silabs_dt_parse_vreg_info(struct device *dev, + struct fm_power_vreg_data *vreg, const char *vreg_name) +{ + int ret = 0; + u32 vol_suply[2]; + struct device_node *np = dev->of_node; + + ret = of_property_read_u32_array(np, vreg_name, vol_suply, 2); + if (ret < 0) { + FMDERR("Invalid property name\n"); + ret = -EINVAL; + } else { + vreg->low_vol_level = vol_suply[0]; + vreg->high_vol_level = vol_suply[1]; + } + return ret; +} + +static int silabs_fm_vidioc_g_fmt_type_private(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; + +} + +static const struct v4l2_ioctl_ops silabs_fm_ioctl_ops = { + .vidioc_querycap = silabs_fm_vidioc_querycap, + .vidioc_queryctrl = silabs_fm_vidioc_queryctrl, + .vidioc_g_ctrl = silabs_fm_vidioc_g_ctrl, + .vidioc_s_ctrl = silabs_fm_vidioc_s_ctrl, + .vidioc_g_tuner = silabs_fm_vidioc_g_tuner, + .vidioc_s_tuner = silabs_fm_vidioc_s_tuner, + .vidioc_g_frequency = silabs_fm_vidioc_g_frequency, + .vidioc_s_frequency = silabs_fm_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = silabs_fm_vidioc_s_hw_freq_seek, + .vidioc_dqbuf = silabs_fm_vidioc_dqbuf, + .vidioc_g_fmt_type_private = silabs_fm_vidioc_g_fmt_type_private, +}; + +static const struct v4l2_file_operations silabs_fm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = v4l2_compat_ioctl32, +#endif + .open = silabs_fm_fops_open, + .release = silabs_fm_fops_release, +}; + + +static const struct video_device silabs_fm_viddev_template = { + .fops = &silabs_fm_fops, + .ioctl_ops = &silabs_fm_ioctl_ops, + .name = DRIVER_NAME, + .release = video_device_release, +}; + +static int silabs_fm_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + + struct silabs_fm_device *radio; + struct regulator *vreg = NULL; + int retval = 0; + int i = 0; + int kfifo_alloc_rc = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + FMDERR("%s: no support for i2c read/write byte data\n", + __func__); + return -EIO; + } + + vreg = regulator_get(&client->dev, "va"); + + if (IS_ERR(vreg)) { + /* + * if analog voltage regulator, VA is not ready yet, return + * -EPROBE_DEFER to kernel so that probe will be called at + * later point of time. + */ + if (PTR_ERR(vreg) == -EPROBE_DEFER) { + FMDERR("In %s, areg probe defer\n", __func__); + return PTR_ERR(vreg); + } + } + /* private data allocation */ + radio = kzalloc(sizeof(struct silabs_fm_device), GFP_KERNEL); + if (!radio) { + FMDERR("Memory not allocated for radio\n"); + retval = -ENOMEM; + goto err_initial; + } + + retval = silabs_parse_dt(&client->dev, radio); + if (retval) { + FMDERR("%s: Parsing DT failed(%d)", __func__, retval); + regulator_put(vreg); + kfree(radio); + return retval; + } + + radio->client = client; + + i2c_set_clientdata(client, radio); + if (!IS_ERR(vreg)) { + radio->areg = devm_kzalloc(&client->dev, + sizeof(struct fm_power_vreg_data), + GFP_KERNEL); + if (!radio->areg) { + FMDERR("%s: allocating memory for areg failed\n", + __func__); + regulator_put(vreg); + kfree(radio); + return -ENOMEM; + } + + radio->areg->reg = vreg; + radio->areg->name = "va"; + radio->areg->is_enabled = 0; + retval = silabs_dt_parse_vreg_info(&client->dev, + radio->areg, "silabs,va-supply-voltage"); + if (retval < 0) { + FMDERR("%s: parsing va-supply failed\n", __func__); + goto mem_alloc_fail; + } + } + + vreg = regulator_get(&client->dev, "vdd"); + + if (IS_ERR(vreg)) { + FMDERR("In %s, vdd supply is not provided\n", __func__); + } else { + radio->dreg = devm_kzalloc(&client->dev, + sizeof(struct fm_power_vreg_data), + GFP_KERNEL); + if (!radio->dreg) { + FMDERR("%s: allocating memory for dreg failed\n", + __func__); + retval = -ENOMEM; + regulator_put(vreg); + goto mem_alloc_fail; + } + + radio->dreg->reg = vreg; + radio->dreg->name = "vdd"; + radio->dreg->is_enabled = 0; + retval = silabs_dt_parse_vreg_info(&client->dev, + radio->dreg, "silabs,vdd-supply-voltage"); + if (retval < 0) { + FMDERR("%s: parsing vdd-supply failed\n", __func__); + goto err_dreg; + } + } + + /* Initialize pin control*/ + retval = silabs_fm_pinctrl_init(radio); + if (retval) { + FMDERR("%s: silabs_fm_pinctrl_init returned %d\n", + __func__, retval); + /* if pinctrl is not supported, -EINVAL is returned*/ + if (retval == -EINVAL) + retval = 0; + } else { + FMDBG("silabs_fm_pinctrl_init success\n"); + } + + radio->wqueue = NULL; + radio->wqueue_scan = NULL; + radio->wqueue_af = NULL; + radio->wqueue_rds = NULL; + + /* video device allocation */ + radio->videodev = video_device_alloc(); + if (!radio->videodev) { + FMDERR("radio->videodev is NULL\n"); + goto err_dreg; + } + /* initial configuration */ + memcpy(radio->videodev, &silabs_fm_viddev_template, + sizeof(silabs_fm_viddev_template)); + strlcpy(radio->v4l2_dev.name, DRIVER_NAME, + sizeof(radio->v4l2_dev.name)); + retval = v4l2_device_register(NULL, &radio->v4l2_dev); + if (retval) + goto err_dreg; + radio->videodev->v4l2_dev = &radio->v4l2_dev; + + /*allocate internal buffers for decoded rds and event buffer*/ + for (i = 0; i < SILABS_FM_BUF_MAX; i++) { + spin_lock_init(&radio->buf_lock[i]); + + if (i == SILABS_FM_BUF_RAW_RDS) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + FM_RDS_BUF * 3, GFP_KERNEL); + else if (i == SILABS_FM_BUF_RT_RDS) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE * 2, GFP_KERNEL); + else + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE, GFP_KERNEL); + + if (kfifo_alloc_rc != 0) { + FMDERR("%s: failed allocating buffers %d\n", + __func__, kfifo_alloc_rc); + retval = -ENOMEM; + goto err_fifo_alloc; + } + } + /* initializing the device count */ + atomic_set(&radio->users, 1); + + /* radio initializes to normal mode */ + radio->lp_mode = 0; + radio->handle_irq = 1; + radio->af_wait_timer = AF_WAIT_SEC; + /* init lock */ + mutex_init(&radio->lock); + radio->tune_req = 0; + radio->seek_tune_status = 0; + init_completion(&radio->sync_req_done); + /* initialize wait queue for event read */ + init_waitqueue_head(&radio->event_queue); + /* initialize wait queue for raw rds read */ + init_waitqueue_head(&radio->read_queue); + + video_set_drvdata(radio->videodev, radio); + + /* + * Start the worker thread for event handling and register read_int_stat + * as worker function + */ + radio->wqueue = create_singlethread_workqueue("sifmradio"); + + if (!radio->wqueue) { + retval = -ENOMEM; + goto err_fifo_alloc; + } + + FMDBG("%s: creating work q for scan\n", __func__); + radio->wqueue_scan = create_singlethread_workqueue("sifmradioscan"); + + if (!radio->wqueue_scan) { + retval = -ENOMEM; + goto err_wqueue_scan; + } + + FMDBG("%s: creating work q for af\n", __func__); + radio->wqueue_af = create_singlethread_workqueue("sifmradioaf"); + + if (!radio->wqueue_af) { + retval = -ENOMEM; + goto err_wqueue_af; + } + + radio->wqueue_rds = create_singlethread_workqueue("sifmradiords"); + + if (!radio->wqueue_rds) { + retval = -ENOMEM; + goto err_wqueue_rds; + } + + /* register video device */ + retval = video_register_device(radio->videodev, + VFL_TYPE_RADIO, + RADIO_NR); + if (retval != 0) { + FMDERR("Could not register video device\n"); + goto err_all; + } + return 0; + +err_all: + destroy_workqueue(radio->wqueue_rds); +err_wqueue_rds: + destroy_workqueue(radio->wqueue_af); +err_wqueue_af: + destroy_workqueue(radio->wqueue_scan); +err_wqueue_scan: + destroy_workqueue(radio->wqueue); +err_fifo_alloc: + for (i--; i >= 0; i--) + kfifo_free(&radio->data_buf[i]); + video_device_release(radio->videodev); +err_dreg: + if (radio->dreg && radio->dreg->reg) { + regulator_put(radio->dreg->reg); + devm_kfree(&client->dev, radio->dreg); + } +mem_alloc_fail: + if (radio->areg && radio->areg->reg) { + regulator_put(radio->areg->reg); + devm_kfree(&client->dev, radio->areg); + } + kfree(radio); +err_initial: + return retval; +} + +static int silabs_fm_remove(struct i2c_client *client) +{ + int i; + struct silabs_fm_device *radio = i2c_get_clientdata(client); + + if (unlikely(radio == NULL)) { + FMDERR("%s:radio is null", __func__); + return -EINVAL; + } + + if (radio->dreg && radio->dreg->reg) { + regulator_put(radio->dreg->reg); + devm_kfree(&client->dev, radio->dreg); + } + + if (radio->areg && radio->areg->reg) { + regulator_put(radio->areg->reg); + devm_kfree(&client->dev, radio->areg); + } + /* disable irq */ + destroy_workqueue(radio->wqueue); + destroy_workqueue(radio->wqueue_scan); + destroy_workqueue(radio->wqueue_af); + destroy_workqueue(radio->wqueue_rds); + + video_unregister_device(radio->videodev); + + /* free internal buffers */ + for (i = 0; i < SILABS_FM_BUF_MAX; i++) + kfifo_free(&radio->data_buf[i]); + + /* free state struct */ + kfree(radio); + + return 0; +} + +static const struct i2c_device_id silabs_i2c_id[] = { + { DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, silabs_i2c_id); + +static const struct of_device_id silabs_fm_match[] = { + {.compatible = "silabs,si4705"}, + {} +}; + +static struct i2c_driver silabs_fm_driver = { + .probe = silabs_fm_probe, + .driver = { + .owner = THIS_MODULE, + .name = "silabs-fm", + .of_match_table = silabs_fm_match, + }, + .remove = silabs_fm_remove, + .id_table = silabs_i2c_id, +}; + + +static int __init radio_module_init(void) +{ + return i2c_add_driver(&silabs_fm_driver); +} +module_init(radio_module_init); + +static void __exit radio_module_exit(void) +{ + i2c_del_driver(&silabs_fm_driver); +} +module_exit(radio_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DRIVER_DESC); + diff --git a/drivers/media/radio/silabs/radio-silabs.h b/drivers/media/radio/silabs/radio-silabs.h new file mode 100644 index 000000000000..b602d65a913c --- /dev/null +++ b/drivers/media/radio/silabs/radio-silabs.h @@ -0,0 +1,532 @@ +/* Copyright (c) 2014, 2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __RADIO_SILABS_H +#define __RADIO_SILABS_H + +#include <linux/ioctl.h> +#include <linux/videodev2.h> + +#define WRITE_REG_NUM 8 +#define READ_REG_NUM 16 + +#define FMDBG(fmt, args...) pr_debug("silabs_radio: " fmt, ##args) + +#define FMDERR(fmt, args...) pr_err("silabs_radio: " fmt, ##args) + +/* For bounds checking. */ +static const unsigned char MIN_RDS_STD; +static const unsigned char MAX_RDS_STD = 0x02; +static const unsigned char MIN_SRCH_MODE; +static const unsigned char MAX_SRCH_MODE = 0x02; + +/* Standard buffer size */ +#define STD_BUF_SIZE (256) +/* Search direction */ +#define SRCH_DIR_UP (1) +#define SRCH_DIR_DOWN (0) +#define WAIT_TIMEOUT_MSEC 2000 +#define SILABS_DELAY_MSEC 10 +#define CTS_RETRY_COUNT 10 +#define RADIO_NR -1 +#define TURNING_ON 1 +#define TURNING_OFF 0 +#define CH_SPACING_200 200 +#define CH_SPACING_100 100 +#define CH_SPACING_50 50 + +/* to distinguish between seek, tune during STC int. */ +#define NO_SEEK_TUNE_PENDING 0 +#define TUNE_PENDING 1 +#define SEEK_PENDING 2 +#define SCAN_PENDING 3 +#define WRAP_ENABLE 1 +#define WRAP_DISABLE 0 +#define VALID_MASK 0x01 +/* it will check whether UPPER band reached or not */ +#define BLTF_MASK 0x80 +#define SAMPLE_RATE_48_KHZ 0xBB80 +#define MIN_DWELL_TIME 0x00 +#define MAX_DWELL_TIME 0x0F +#define START_SCAN 1 +#define GET_MSB(x)((x >> 8) & 0xFF) +#define GET_LSB(x)((x) & 0xFF) +/* + * When tuning, we need to divide freq by TUNE_STEP_SIZE + * before sending it to chip + */ +#define TUNE_STEP_SIZE 10 + +#define TUNE_PARAM 16 + +#define OFFSET_OF_GRP_TYP 11 +#define RDS_INT_BIT 0x01 +#define FIFO_CNT_16 0x10 +#define UNCORRECTABLE_RDS_EN 0xFF01 +#define NO_OF_RDS_BLKS 4 +#define MSB_OF_BLK_0 4 +#define LSB_OF_BLK_0 5 +#define MSB_OF_BLK_1 6 +#define LSB_OF_BLK_1 7 +#define MSB_OF_BLK_2 8 +#define LSB_OF_BLK_2 9 +#define MSB_OF_BLK_3 10 +#define LSB_OF_BLK_3 11 +#define MAX_RT_LEN 64 +#define END_OF_RT 0x0d +#define MAX_PS_LEN 8 +#define OFFSET_OF_PS 5 +#define PS_VALIDATE_LIMIT 2 +#define RT_VALIDATE_LIMIT 2 +#define RDS_CMD_LEN 3 +#define RDS_RSP_LEN 13 +#define PS_EVT_DATA_LEN (MAX_PS_LEN + OFFSET_OF_PS) +#define NO_OF_PS 1 +#define OFFSET_OF_RT 5 +#define OFFSET_OF_PTY 5 +#define MAX_LEN_2B_GRP_RT 32 +#define CNT_FOR_2A_GRP_RT 4 +#define CNT_FOR_2B_GRP_RT 2 +#define PS_MASK 0x3 +#define PTY_MASK 0x1F +#define NO_OF_CHARS_IN_EACH_ADD 2 + +#define MIN_SNR 0 +#define MAX_SNR 127 +#define MIN_RSSI 0 +#define MAX_RSSI 127 +#define MIN_RDS_FIFO_CNT 0 +#define MAX_RDS_FIFO_CNT 25 + +#define DEFAULT_SNR_TH 2 +#define DEFAULT_RSSI_TH 7 + +#define DEFAULT_AF_RSSI_LOW_TH 25 +#define NO_OF_AF_IN_GRP 2 +#define MAX_NO_OF_AF 25 +#define MAX_AF_LIST_SIZE (MAX_NO_OF_AF * 4) /* 4 bytes per freq */ +#define GET_AF_EVT_LEN(x) (7 + x*4) +#define GET_AF_LIST_LEN(x) (x*4) +#define MIN_AF_FREQ_CODE 1 +#define MAX_AF_FREQ_CODE 204 +/* 25 AFs supported for a freq. 224 means 1 AF. 225 means 2 AFs and so on */ +#define NO_AF_CNT_CODE 224 +#define MIN_AF_CNT_CODE 225 +#define MAX_AF_CNT_CODE 249 +#define AF_WAIT_SEC 10 +#define MAX_AF_WAIT_SEC 255 +#define AF_PI_WAIT_TIME 50 /* 50*100msec = 5sec */ +/* freqs are divided by 10. */ +#define SCALE_AF_CODE_TO_FREQ_KHZ(x) (87500 + (x*100)) + + +/* commands */ +#define POWER_UP_CMD 0x01 +#define GET_REV_CMD 0x10 +#define POWER_DOWN_CMD 0x11 +#define SET_PROPERTY_CMD 0x12 +#define GET_PROPERTY_CMD 0x13 +#define GET_INT_STATUS_CMD 0x14 +#define PATCH_ARGS_CMD 0x15 +#define PATCH_DATA_CMD 0x16 +#define FM_TUNE_FREQ_CMD 0x20 +#define FM_SEEK_START_CMD 0x21 +#define FM_TUNE_STATUS_CMD 0x22 +#define FM_RSQ_STATUS_CMD 0x23 +#define FM_RDS_STATUS_CMD 0x24 +#define FM_AGC_STATUS_CMD 0x27 +#define FM_AGC_OVERRIDE_CMD 0x28 +#define GPIO_CTL_CMD 0x80 +#define GPIO_SET_CMD 0x81 + +/* properties */ +#define GPO_IEN_PROP 0x0001 +#define DIGITAL_OUTPUT_FORMAT_PROP 0x0102 +#define DIGITAL_OUTPUT_SAMPLE_RATE_PROP 0x0104 +#define REFCLK_FREQ_PROP 0x0201 +#define REFCLK_PRESCALE_PROP 0x0202 +#define FM_DEEMPHASIS_PROP 0x1100 +#define FM_CHANNEL_FILTER_PROP 0x1102 +#define FM_ANTENNA_INPUT_PROP 0x1107 +#define FM_RSQ_INT_SOURCE_PROP 0x1200 +#define FM_RSQ_RSSI_LO_THRESHOLD_PROP 0x1204 + +#define FM_SEEK_BAND_BOTTOM_PROP 0x1400 +#define FM_SEEK_BAND_TOP_PROP 0x1401 +#define FM_SEEK_FREQ_SPACING_PROP 0x1402 +#define FM_SEEK_TUNE_SNR_THRESHOLD_PROP 0x1403 +#define FM_SEEK_TUNE_RSSI_THRESHOLD_PROP 0x1404 + +#define FM_RDS_INT_SOURCE_PROP 0x1500 +#define FM_RDS_INT_FIFO_COUNT_PROP 0x1501 +#define FM_RDS_CONFIG_PROP 0x1502 + +#define RX_HARD_MUTE_PROP 0x4001 + +/* BIT MASKS */ +#define ENABLE_CTS_INT_MASK (1 << 7) +#define ENABLE_GPO2_INT_MASK (1 << 6) +#define PATCH_ENABLE_MASK (1 << 5) +/* to use clock present on daughter card or MSM's */ +#define CLOCK_ENABLE_MASK (1 << 4) +#define FUNC_QUERY_LIB_ID_MASK 15 +#define CANCEL_SEEK_MASK (1 << 1) +#define INTACK_MASK 1 +#define SEEK_WRAP_MASK (1 << 2) +#define SEEK_UP_MASK (1 << 3) + + +/* BIT MASKS to parse response bytes */ +#define CTS_INT_BIT_MASK (1 << 7) +#define ERR_BIT_MASK (1 << 6) +#define RSQ_INT_BIT_MASK (1 << 3) +/* set RDS repeat int bit along with RDS int bit */ +#define RDS_INT_BIT_MASK (0x0404) +#define STC_INT_BIT_MASK 1 +#define RSSI_LOW_TH_INT_BIT_MASK 1 +#define RDS_INT_DISABLE_MASK 0x9 +#define RSQ_INT_DISABLE_MASK 0x5 +#define HARD_MUTE_MASK 0x3 +#define GPIO1_OUTPUT_ENABLE_MASK (1 << 1) +#define GPIO_OUTPUT_LOW_MASK 0 + +#define DCLK_FALLING_EDGE_MASK (1 << 7) + +/* Command lengths */ +#define SET_PROP_CMD_LEN 6 +#define GET_PROP_CMD_LEN 4 +#define GET_INT_STATUS_CMD_LEN 1 +#define POWER_UP_CMD_LEN 3 +#define POWER_DOWN_CMD_LEN 1 +#define TUNE_FREQ_CMD_LEN 5 +#define SEEK_CMD_LEN 2 +#define TUNE_STATUS_CMD_LEN 2 +#define RSQ_STATUS_CMD_LEN 2 +#define GPIO_CTL_CMD_LEN 2 +#define GPIO_SET_CMD_LEN 2 + +#define HIGH_BYTE_16BIT(x) (x >> 8) +#define LOW_BYTE_16BIT(x) (x & 0xFF) + + +#define AUDIO_OPMODE_ANALOG 0x05 +#define AUDIO_OPMODE_DIGITAL 0xB0 + +/* ERROR codes */ +#define BAD_CMD 0x10 +#define BAD_ARG1 0x11 +#define BAD_ARG2 0x12 +#define BAD_ARG3 0x13 +#define BAD_ARG4 0x14 +#define BAD_ARG5 0x15 +#define BAD_ARG6 0x16 +#define BAD_ARG7 0x17 +#define BAD_PROP 0x20 +#define BAD_BOOT_MODE 0x30 + +/* RDS */ +#define FM_RDS_BUF 100 +#define FM_RDS_STATUS_IN_INTACK 0x01 +#define FM_RDS_STATUS_IN_MTFIFO 0x02 +#define FM_RDS_STATUS_OUT_GRPLOST 0x04 +#define FM_RDS_STATUS_OUT_BLED 0x03 +#define FM_RDS_STATUS_OUT_BLEC 0x0C +#define FM_RDS_STATUS_OUT_BLEB 0x30 +#define FM_RDS_STATUS_OUT_BLEA 0xC0 +#define FM_RDS_STATUS_OUT_BLED_SHFT 0 +#define FM_RDS_STATUS_OUT_BLEC_SHFT 2 +#define FM_RDS_STATUS_OUT_BLEB_SHFT 4 +#define FM_RDS_STATUS_OUT_BLEA_SHFT 6 +#define RDS_TYPE_0A (0 * 2 + 0) +#define RDS_TYPE_0B (0 * 2 + 1) +#define RDS_TYPE_2A (2 * 2 + 0) +#define RDS_TYPE_2B (2 * 2 + 1) +#define RDS_TYPE_3A (3 * 2 + 0) +#define UNCORRECTABLE 3 +#define RT_VALIDATE_LIMIT 2 + +#define APP_GRP_typ_MASK 0x1F +/*ERT*/ +#define ERT_AID 0x6552 +#define MAX_ERT_SEGMENT 31 +#define MAX_ERT_LEN 256 +#define ERT_OFFSET 3 +#define ERT_FORMAT_DIR_BIT 1 +#define ERT_CNT_PER_BLK 2 +/*RT PLUS*/ +#define DUMMY_CLASS 0 +#define RT_PLUS_LEN_1_TAG 3 +#define RT_ERT_FLAG_BIT 13 +#define RT_PLUS_AID 0x4bd7 +#define RT_ERT_FLAG_OFFSET 1 +#define RT_PLUS_OFFSET 2 +/*TAG1*/ +#define TAG1_MSB_OFFSET 3 +#define TAG1_MSB_MASK 7 +#define TAG1_LSB_OFFSET 13 +#define TAG1_POS_MSB_MASK 0x3F +#define TAG1_POS_MSB_OFFSET 1 +#define TAG1_POS_LSB_OFFSET 7 +#define TAG1_LEN_OFFSET 1 +#define TAG1_LEN_MASK 0x3F +/*TAG2*/ +#define TAG2_MSB_OFFSET 5 +#define TAG2_MSB_MASK 9 +#define TAG2_LSB_OFFSET 11 +#define TAG2_POS_MSB_MASK 0x3F +#define TAG2_POS_MSB_OFFSET 3 +#define TAG2_POS_LSB_OFFSET 5 +#define TAG2_LEN_MASK 0x1F + +#define EXTRACT_BIT(data, bit_pos) ((data >> bit_pos) & 1) + +/* FM states */ +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_RESET, + FM_CALIB, + FM_TURNING_OFF, + FM_RECV_TURNING_ON, + FM_MAX_NO_STATES, +}; + +enum emphasis_type { + FM_RX_EMP75 = 0x0002, + FM_RX_EMP50 = 0x0001 +}; + +/* 3 valid values: 5 (50 kHz), 10 (100 kHz), and 20 (200 kHz). */ +enum channel_space_type { + FM_RX_SPACE_200KHZ = 0x0014, + FM_RX_SPACE_100KHZ = 0x000A, + FM_RX_SPACE_50KHZ = 0x0005 +}; + +enum v4l2_cid_private_silabs_fm_t { + V4L2_CID_PRIVATE_SILABS_SRCHMODE = (V4L2_CID_PRIVATE_BASE + 1), + V4L2_CID_PRIVATE_SILABS_SCANDWELL, + V4L2_CID_PRIVATE_SILABS_SRCHON, + V4L2_CID_PRIVATE_SILABS_STATE, + V4L2_CID_PRIVATE_SILABS_TRANSMIT_MODE, + V4L2_CID_PRIVATE_SILABS_RDSGROUP_MASK, + V4L2_CID_PRIVATE_SILABS_REGION, + V4L2_CID_PRIVATE_SILABS_SIGNAL_TH, + V4L2_CID_PRIVATE_SILABS_SRCH_PTY, + V4L2_CID_PRIVATE_SILABS_SRCH_PI, + V4L2_CID_PRIVATE_SILABS_SRCH_CNT, + V4L2_CID_PRIVATE_SILABS_EMPHASIS, + V4L2_CID_PRIVATE_SILABS_RDS_STD, + V4L2_CID_PRIVATE_SILABS_SPACING, + V4L2_CID_PRIVATE_SILABS_RDSON, + V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC, + V4L2_CID_PRIVATE_SILABS_LP_MODE, + V4L2_CID_PRIVATE_SILABS_ANTENNA, + V4L2_CID_PRIVATE_SILABS_RDSD_BUF, + V4L2_CID_PRIVATE_SILABS_PSALL, + /*v4l2 Tx controls*/ + V4L2_CID_PRIVATE_SILABS_TX_SETPSREPEATCOUNT, + V4L2_CID_PRIVATE_SILABS_STOP_RDS_TX_PS_NAME, + V4L2_CID_PRIVATE_SILABS_STOP_RDS_TX_RT, + V4L2_CID_PRIVATE_SILABS_IOVERC, + V4L2_CID_PRIVATE_SILABS_INTDET, + V4L2_CID_PRIVATE_SILABS_MPX_DCC, + V4L2_CID_PRIVATE_SILABS_AF_JUMP, + V4L2_CID_PRIVATE_SILABS_RSSI_DELTA, + V4L2_CID_PRIVATE_SILABS_HLSI, + + /* + * Here we have IOCTl's that are specific to IRIS + * (V4L2_CID_PRIVATE_BASE + 0x1E to V4L2_CID_PRIVATE_BASE + 0x28) + */ + V4L2_CID_PRIVATE_SILABS_SOFT_MUTE,/* 0x800001E*/ + V4L2_CID_PRIVATE_SILABS_RIVA_ACCS_ADDR, + V4L2_CID_PRIVATE_SILABS_RIVA_ACCS_LEN, + V4L2_CID_PRIVATE_SILABS_RIVA_PEEK, + V4L2_CID_PRIVATE_SILABS_RIVA_POKE, + V4L2_CID_PRIVATE_SILABS_SSBI_ACCS_ADDR, + V4L2_CID_PRIVATE_SILABS_SSBI_PEEK, + V4L2_CID_PRIVATE_SILABS_SSBI_POKE, + V4L2_CID_PRIVATE_SILABS_TX_TONE, + V4L2_CID_PRIVATE_SILABS_RDS_GRP_COUNTERS, + V4L2_CID_PRIVATE_SILABS_SET_NOTCH_FILTER,/* 0x8000028 */ + + V4L2_CID_PRIVATE_SILABS_SET_AUDIO_PATH,/* 0x8000029 */ + V4L2_CID_PRIVATE_SILABS_DO_CALIBRATION,/* 0x800002A : IRIS */ + V4L2_CID_PRIVATE_SILABS_SRCH_ALGORITHM,/* 0x800002B */ + V4L2_CID_PRIVATE_SILABS_GET_SINR, /* 0x800002C : IRIS */ + V4L2_CID_PRIVATE_SILABS_INTF_LOW_THRESHOLD, /* 0x800002D */ + V4L2_CID_PRIVATE_SILABS_INTF_HIGH_THRESHOLD, /* 0x800002E */ + V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD, /* 0x800002F : IRIS */ + V4L2_CID_PRIVATE_SILABS_SINR_SAMPLES, /* 0x8000030 : IRIS */ + V4L2_CID_PRIVATE_SILABS_SPUR_FREQ, + V4L2_CID_PRIVATE_SILABS_SPUR_FREQ_RMSSI, + V4L2_CID_PRIVATE_SILABS_SPUR_SELECTION, + V4L2_CID_PRIVATE_SILABS_UPDATE_SPUR_TABLE, + V4L2_CID_PRIVATE_SILABS_VALID_CHANNEL, + V4L2_CID_PRIVATE_SILABS_AF_RMSSI_TH, + V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES, + V4L2_CID_PRIVATE_SILABS_GOOD_CH_RMSSI_TH, + V4L2_CID_PRIVATE_SILABS_SRCHALGOTYPE, + V4L2_CID_PRIVATE_SILABS_CF0TH12, + V4L2_CID_PRIVATE_SILABS_SINRFIRSTSTAGE, + V4L2_CID_PRIVATE_SILABS_RMSSIFIRSTSTAGE, + V4L2_CID_PRIVATE_SILABS_RXREPEATCOUNT, + V4L2_CID_PRIVATE_SILABS_RSSI_TH, /* 0x800003E */ + V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH /* 0x800003F */ + +}; + +enum silabs_buf_t { + SILABS_FM_BUF_SRCH_LIST, + SILABS_FM_BUF_EVENTS, + SILABS_FM_BUF_RT_RDS, + SILABS_FM_BUF_PS_RDS, + SILABS_FM_BUF_RAW_RDS, + SILABS_FM_BUF_AF_LIST, + SILABS_FM_BUF_RT_PLUS = 11, + SILABS_FM_BUF_ERT, + SILABS_FM_BUF_MAX +}; + +enum silabs_evt_t { + SILABS_EVT_RADIO_READY, + SILABS_EVT_TUNE_SUCC, + SILABS_EVT_SEEK_COMPLETE, + SILABS_EVT_SCAN_NEXT, + SILABS_EVT_NEW_RAW_RDS, + SILABS_EVT_NEW_RT_RDS, + SILABS_EVT_NEW_PS_RDS, + SILABS_EVT_ERROR, + SILABS_EVT_BELOW_TH, + SILABS_EVT_ABOVE_TH, + SILABS_EVT_STEREO, + SILABS_EVT_MONO, + SILABS_EVT_RDS_AVAIL, + SILABS_EVT_RDS_NOT_AVAIL, + SILABS_EVT_NEW_SRCH_LIST, + SILABS_EVT_NEW_AF_LIST, + SILABS_EVT_TXRDSDAT, + SILABS_EVT_TXRDSDONE, + SILABS_EVT_RADIO_DISABLED, + SILABS_EVT_NEW_ODA, + SILABS_EVT_NEW_RT_PLUS, + SILABS_EVT_NEW_ERT +}; + +enum silabs_region_t { + SILABS_REGION_US, + SILABS_REGION_EU, + SILABS_REGION_JAPAN, + SILABS_REGION_JAPAN_WIDE, + SILABS_REGION_OTHER +}; + +enum silabs_interrupts_t { + DISABLE_ALL_INTERRUPTS, + ENABLE_STC_RDS_INTERRUPTS, + ENABLE_STC_INTERRUPTS, + ENABLE_RDS_INTERRUPTS, + DISABLE_RDS_INTERRUPTS, + ENABLE_RSQ_INTERRUPTS, + DISABLE_RSQ_INTERRUPTS +}; + +struct silabs_fm_recv_conf_req { + __u16 emphasis; + __u16 ch_spacing; + /* limits stored as actual freq / TUNE_STEP_SIZE */ + __u16 band_low_limit; + __u16 band_high_limit; +}; + +struct af_list_ev { + __le32 tune_freq_khz; + __le16 pi_code; + __u8 af_size; + __u8 af_list[MAX_AF_LIST_SIZE]; +} __packed; + +struct silabs_af_info { + /* no. of invalid AFs. */ + u8 inval_freq_cnt; + /* no. of AFs in the list. */ + u8 cnt; + /* actual size of the list */ + u8 size; + /* index of currently tuned station in the AF list. */ + u8 index; + /* PI of the frequency */ + u16 pi; + /* freq to which AF list belongs to. */ + u32 orig_freq_khz; + /* AF list */ + u32 af_list[MAX_NO_OF_AF]; +}; + +static inline bool is_valid_chan_spacing(int spacing) +{ + if ((spacing == 0) || + (spacing == 1) || + (spacing == 2)) + return 1; + else + return 0; +} + +static inline bool is_valid_rds_std(int rds_std) +{ + if ((rds_std >= MIN_RDS_STD) && + (rds_std <= MAX_RDS_STD)) + return 1; + else + return 0; +} + +static inline bool is_valid_srch_mode(int srch_mode) +{ + if ((srch_mode >= MIN_SRCH_MODE) && + (srch_mode <= MAX_SRCH_MODE)) + return 1; + else + return 0; +} + +struct fm_power_vreg_data { + /* voltage regulator handle */ + struct regulator *reg; + /* regulator name */ + const char *name; + /* voltage levels to be set */ + unsigned int low_vol_level; + unsigned int high_vol_level; + bool set_voltage_sup; + /* is this regulator enabled? */ + bool is_enabled; +}; + +struct silabs_rel_freq { + __u8 rel_freq_msb; + __u8 rel_freq_lsb; +} __packed; + +struct silabs_srch_list_compl { + __u8 num_stations_found; + struct silabs_rel_freq rel_freq[20]; +} __packed; + +enum search_t { + SEEK, + SCAN, + SCAN_FOR_STRONG, +}; +#endif /* __RADIO_SILABS_H */ diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index 6b1eaeddbdb3..02532c8c69ca 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -706,6 +706,9 @@ static void determine_valid_ioctls(struct video_device *vdev) SET_VALID_IOCTL(ops, VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings); SET_VALID_IOCTL(ops, VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap); SET_VALID_IOCTL(ops, VIDIOC_G_EDID, vidioc_g_edid); + } else { + /* ioctls valid for radio */ + SET_VALID_IOCTL(ops, VIDIOC_DQBUF, vidioc_dqbuf); } if (is_tx && (is_radio || is_sdr)) { /* radio transmitter only ioctls */ diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 79829f56a816..2fd84486c054 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -980,6 +980,10 @@ static int check_fmt(struct file *file, enum v4l2_buf_type type) if (is_sdr && is_tx && ops->vidioc_g_fmt_sdr_out) return 0; break; + case V4L2_BUF_TYPE_PRIVATE: + if (ops->vidioc_g_fmt_type_private) + return 0; + break; default: break; } diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_rt.c b/drivers/platform/msm/ipa/ipa_v2/ipa_rt.c index af8d9dcf8afc..007f92bcee13 100644 --- a/drivers/platform/msm/ipa/ipa_v2/ipa_rt.c +++ b/drivers/platform/msm/ipa/ipa_v2/ipa_rt.c @@ -1066,9 +1066,8 @@ static int __ipa_add_rt_rule(enum ipa_ip_type ip, const char *name, * tables */ if (!strncmp(tbl->name, IPA_DFLT_RT_TBL_NAME, IPA_RESOURCE_NAME_MAX) && - (tbl->rule_cnt > 0) && (at_rear != 0)) { - IPAERR("cannot add rule at end of tbl rule_cnt=%d at_rear=%d\n", - tbl->rule_cnt, at_rear); + (tbl->rule_cnt > 0)) { + IPAERR("cannot add rules to default rt table\n"); goto error; } @@ -1605,6 +1604,11 @@ static int __ipa_mdfy_rt_rule(struct ipa_rt_rule_mdfy *rtrule) goto error; } + if (!strcmp(entry->tbl->name, IPA_DFLT_RT_TBL_NAME)) { + IPAERR_RL("Default tbl rule cannot be modified\n"); + return -EINVAL; + } + /* Adding check to confirm still * header entry present in header table or not */ diff --git a/drivers/power/supply/qcom/qpnp-smbcharger.c b/drivers/power/supply/qcom/qpnp-smbcharger.c index 6abe0e021877..a43aae4b2d0f 100644 --- a/drivers/power/supply/qcom/qpnp-smbcharger.c +++ b/drivers/power/supply/qcom/qpnp-smbcharger.c @@ -277,6 +277,7 @@ struct smbchg_chip { bool skip_usb_notification; u32 vchg_adc_channel; struct qpnp_vadc_chip *vchg_vadc_dev; + struct qpnp_vadc_chip *vusb_vadc_dev; /* voters */ struct votable *fcc_votable; @@ -5744,9 +5745,43 @@ static void update_typec_otg_status(struct smbchg_chip *chip, int mode, } } +static int smbchg_get_vusb(struct smbchg_chip *chip) +{ + int rc; + struct qpnp_vadc_result adc_result; + + if (!is_usb_present(chip)) + return 0; + + if (chip->vusb_vadc_dev) { + rc = qpnp_vadc_read(chip->vusb_vadc_dev, + USBIN, &adc_result); + if (rc < 0) { + pr_smb(PR_STATUS, + "error in vusb_uv read rc = %d\n", rc); + return rc; + } + } else { + return -ENODATA; + } + pr_smb(PR_STATUS, "The value of vusb_uv %lld\n", adc_result.physical); + + return adc_result.physical; +} + static int smbchg_set_sdp_current(struct smbchg_chip *chip, int current_ma) { if (chip->usb_supply_type == POWER_SUPPLY_TYPE_USB) { + if (current_ma == -ETIMEDOUT) { + /* float charger */ + current_ma = CURRENT_1500_MA; + pr_smb(PR_MISC, + "Update usb_type to FLOAT current=%dmA\n", + current_ma); + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB_FLOAT; + } else { + current_ma = current_ma / 1000; + } /* Override if type-c charger used */ if (chip->typec_current_ma > 500 && current_ma < chip->typec_current_ma) { @@ -5789,6 +5824,9 @@ static int smbchg_usb_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_HEALTH: val->intval = chip->usb_health; break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = smbchg_get_vusb(chip); + break; default: return -EINVAL; } @@ -5807,7 +5845,7 @@ static int smbchg_usb_set_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: - smbchg_set_sdp_current(chip, val->intval / 1000); + smbchg_set_sdp_current(chip, val->intval); default: return -EINVAL; } @@ -5844,6 +5882,7 @@ static enum power_supply_property smbchg_usb_properties[] = { POWER_SUPPLY_PROP_REAL_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_SDP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, }; #define CHARGE_OUTPUT_VTG_RATIO 840 @@ -8191,6 +8230,7 @@ static int smbchg_probe(struct platform_device *pdev) struct smbchg_chip *chip; struct power_supply *typec_psy = NULL; struct qpnp_vadc_chip *vadc_dev = NULL, *vchg_vadc_dev = NULL; + struct qpnp_vadc_chip *vusb_vadc_dev = NULL; const char *typec_psy_name; struct power_supply_config usb_psy_cfg = {}; struct power_supply_config batt_psy_cfg = {}; @@ -8239,6 +8279,18 @@ static int smbchg_probe(struct platform_device *pdev) } } + vusb_vadc_dev = NULL; + if (of_find_property(pdev->dev.of_node, "qcom,usbin-vadc", NULL)) { + vusb_vadc_dev = qpnp_get_vadc(&pdev->dev, "usbin"); + if (IS_ERR(vusb_vadc_dev)) { + rc = PTR_ERR(vusb_vadc_dev); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "Couldn't get vadc 'usbin' rc=%d\n", + rc); + return rc; + } + } + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) @@ -8344,6 +8396,7 @@ static int smbchg_probe(struct platform_device *pdev) init_completion(&chip->usbin_uv_raised); chip->vadc_dev = vadc_dev; chip->vchg_vadc_dev = vchg_vadc_dev; + chip->vusb_vadc_dev = vusb_vadc_dev; chip->pdev = pdev; chip->dev = &pdev->dev; diff --git a/drivers/soc/qcom/ipc_router_mhi_xprt.c b/drivers/soc/qcom/ipc_router_mhi_xprt.c index 104053406ea9..9da9d41e2aa6 100644 --- a/drivers/soc/qcom/ipc_router_mhi_xprt.c +++ b/drivers/soc/qcom/ipc_router_mhi_xprt.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -876,6 +876,12 @@ static int ipc_router_mhi_config_init( spin_lock_init(&mhi_xprtp->rx_addr_map_list_lock); rc = ipc_router_mhi_driver_register(mhi_xprtp, dev); + if (rc < 0) { + IPC_RTR_ERR("%s: mhi registration failed\n", __func__); + destroy_workqueue(mhi_xprtp->wq); + kfree(mhi_xprtp); + }; + return rc; } diff --git a/drivers/soc/qcom/smcinvoke.c b/drivers/soc/qcom/smcinvoke.c index f69ff47ae0f7..1d51970df961 100644 --- a/drivers/soc/qcom/smcinvoke.c +++ b/drivers/soc/qcom/smcinvoke.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017,2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -302,7 +302,7 @@ static int marshal_in(const struct smcinvoke_cmd_req *req, const union smcinvoke_arg *args_buf, uint32_t tzhandle, uint8_t *buf, size_t buf_size, struct file **arr_filp) { - int ret = -EINVAL, i = 0; + int ret = -EINVAL, i = 0, j = 0; union smcinvoke_tz_args *tz_args = NULL; struct smcinvoke_msg_hdr msg_hdr = {tzhandle, req->op, req->counts}; uint32_t offset = sizeof(struct smcinvoke_msg_hdr) + @@ -347,7 +347,7 @@ static int marshal_in(const struct smcinvoke_cmd_req *req, } FOR_ARGS(i, req->counts, OI) { if (get_tzhandle_from_fd(args_buf[i].o.fd, - &arr_filp[i], &(tz_args->tzhandle))) + &arr_filp[j++], &(tz_args->tzhandle))) goto out; tz_args++; } diff --git a/drivers/tty/serial/msm_serial_hs.c b/drivers/tty/serial/msm_serial_hs.c index 064494366f01..9a9ea7c62db1 100644 --- a/drivers/tty/serial/msm_serial_hs.c +++ b/drivers/tty/serial/msm_serial_hs.c @@ -3,7 +3,7 @@ * MSM 7k High speed uart driver * * Copyright (c) 2008 Google Inc. - * Copyright (c) 2007-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2007-2019, The Linux Foundation. All rights reserved. * Modified: Nick Pelly <npelly@google.com> * * All source code in this file is licensed under the following license @@ -1185,6 +1185,7 @@ static void msm_hs_set_termios(struct uart_port *uport, data |= EIGHT_BPC; break; } + uport->status &= ~(UPSTAT_AUTOCTS); /* stop bits */ if (c_cflag & CSTOPB) { data |= STOP_BIT_TWO; @@ -1229,6 +1230,7 @@ static void msm_hs_set_termios(struct uart_port *uport, if (c_cflag & CRTSCTS) { data |= UARTDM_MR1_CTS_CTL_BMSK; data |= UARTDM_MR1_RX_RDY_CTL_BMSK; + uport->status |= UPSTAT_AUTOCTS; msm_uport->flow_control = true; } msm_hs_write(uport, UART_DM_MR1, data); |
