summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/media/platform/msm/ais/common/msm_camera_io_util.c8
-rw-r--r--drivers/media/radio/Kconfig7
-rw-r--r--drivers/media/radio/Makefile2
-rw-r--r--drivers/media/radio/silabs/Makefile1
-rw-r--r--drivers/media/radio/silabs/radio-silabs.c3963
-rw-r--r--drivers/media/radio/silabs/radio-silabs.h532
-rw-r--r--drivers/media/v4l2-core/v4l2-dev.c3
-rw-r--r--drivers/media/v4l2-core/v4l2-ioctl.c4
-rw-r--r--drivers/platform/msm/ipa/ipa_v2/ipa_rt.c10
-rw-r--r--drivers/power/supply/qcom/qpnp-smbcharger.c55
-rw-r--r--drivers/soc/qcom/ipc_router_mhi_xprt.c8
-rw-r--r--drivers/soc/qcom/smcinvoke.c6
-rw-r--r--drivers/tty/serial/msm_serial_hs.c4
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);