diff options
Diffstat (limited to 'drivers')
79 files changed, 4980 insertions, 322 deletions
diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c index 0ad96c647541..7017a81d53cf 100644 --- a/drivers/ata/libata-zpodd.c +++ b/drivers/ata/libata-zpodd.c @@ -51,38 +51,52 @@ static int eject_tray(struct ata_device *dev) /* Per the spec, only slot type and drawer type ODD can be supported */ static enum odd_mech_type zpodd_get_mech_type(struct ata_device *dev) { - char buf[16]; + char *buf; unsigned int ret; - struct rm_feature_desc *desc = (void *)(buf + 8); + struct rm_feature_desc *desc; struct ata_taskfile tf; static const char cdb[] = { GPCMD_GET_CONFIGURATION, 2, /* only 1 feature descriptor requested */ 0, 3, /* 3, removable medium feature */ 0, 0, 0,/* reserved */ - 0, sizeof(buf), + 0, 16, 0, 0, 0, }; + buf = kzalloc(16, GFP_KERNEL); + if (!buf) + return ODD_MECH_TYPE_UNSUPPORTED; + desc = (void *)(buf + 8); + ata_tf_init(dev, &tf); tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; tf.command = ATA_CMD_PACKET; tf.protocol = ATAPI_PROT_PIO; - tf.lbam = sizeof(buf); + tf.lbam = 16; ret = ata_exec_internal(dev, &tf, cdb, DMA_FROM_DEVICE, - buf, sizeof(buf), 0); - if (ret) + buf, 16, 0); + if (ret) { + kfree(buf); return ODD_MECH_TYPE_UNSUPPORTED; + } - if (be16_to_cpu(desc->feature_code) != 3) + if (be16_to_cpu(desc->feature_code) != 3) { + kfree(buf); return ODD_MECH_TYPE_UNSUPPORTED; + } - if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) + if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) { + kfree(buf); return ODD_MECH_TYPE_SLOT; - else if (desc->mech_type == 1 && desc->load == 0 && desc->eject == 1) + } else if (desc->mech_type == 1 && desc->load == 0 && + desc->eject == 1) { + kfree(buf); return ODD_MECH_TYPE_DRAWER; - else + } else { + kfree(buf); return ODD_MECH_TYPE_UNSUPPORTED; + } } /* Test if ODD is zero power ready by sense code */ diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index fb2a1e605c86..e5ac568c7c9b 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -705,11 +705,18 @@ ssize_t __weak cpu_show_l1tf(struct device *dev, return sprintf(buf, "Not affected\n"); } +ssize_t __weak cpu_show_mds(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "Not affected\n"); +} + static DEVICE_ATTR(meltdown, 0444, cpu_show_meltdown, NULL); static DEVICE_ATTR(spectre_v1, 0444, cpu_show_spectre_v1, NULL); static DEVICE_ATTR(spectre_v2, 0444, cpu_show_spectre_v2, NULL); static DEVICE_ATTR(spec_store_bypass, 0444, cpu_show_spec_store_bypass, NULL); static DEVICE_ATTR(l1tf, 0444, cpu_show_l1tf, NULL); +static DEVICE_ATTR(mds, 0444, cpu_show_mds, NULL); static struct attribute *cpu_root_vulnerabilities_attrs[] = { &dev_attr_meltdown.attr, @@ -717,6 +724,7 @@ static struct attribute *cpu_root_vulnerabilities_attrs[] = { &dev_attr_spectre_v2.attr, &dev_attr_spec_store_bypass.attr, &dev_attr_l1tf.attr, + &dev_attr_mds.attr, NULL }; diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 91f19dfc388d..1a1b094da6cb 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -82,7 +82,6 @@ static DEFINE_IDR(loop_index_idr); static DEFINE_MUTEX(loop_index_mutex); -static DEFINE_MUTEX(loop_ctl_mutex); static int max_part; static int part_shift; @@ -1045,7 +1044,7 @@ static int loop_clr_fd(struct loop_device *lo) */ if (atomic_read(&lo->lo_refcnt) > 1) { lo->lo_flags |= LO_FLAGS_AUTOCLEAR; - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); return 0; } @@ -1095,12 +1094,12 @@ static int loop_clr_fd(struct loop_device *lo) if (!part_shift) lo->lo_disk->flags |= GENHD_FL_NO_PART_SCAN; loop_unprepare_queue(lo); - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); /* - * Need not hold loop_ctl_mutex to fput backing file. - * Calling fput holding loop_ctl_mutex triggers a circular + * Need not hold lo_ctl_mutex to fput backing file. + * Calling fput holding lo_ctl_mutex triggers a circular * lock dependency possibility warning as fput can take - * bd_mutex which is usually taken before loop_ctl_mutex. + * bd_mutex which is usually taken before lo_ctl_mutex. */ fput(filp); return 0; @@ -1413,7 +1412,7 @@ static int lo_ioctl(struct block_device *bdev, fmode_t mode, struct loop_device *lo = bdev->bd_disk->private_data; int err; - mutex_lock_nested(&loop_ctl_mutex, 1); + mutex_lock_nested(&lo->lo_ctl_mutex, 1); switch (cmd) { case LOOP_SET_FD: err = loop_set_fd(lo, mode, bdev, arg); @@ -1422,7 +1421,7 @@ static int lo_ioctl(struct block_device *bdev, fmode_t mode, err = loop_change_fd(lo, bdev, arg); break; case LOOP_CLR_FD: - /* loop_clr_fd would have unlocked loop_ctl_mutex on success */ + /* loop_clr_fd would have unlocked lo_ctl_mutex on success */ err = loop_clr_fd(lo); if (!err) goto out_unlocked; @@ -1463,7 +1462,7 @@ static int lo_ioctl(struct block_device *bdev, fmode_t mode, default: err = lo->ioctl ? lo->ioctl(lo, cmd, arg) : -EINVAL; } - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); out_unlocked: return err; @@ -1596,16 +1595,16 @@ static int lo_compat_ioctl(struct block_device *bdev, fmode_t mode, switch(cmd) { case LOOP_SET_STATUS: - mutex_lock(&loop_ctl_mutex); + mutex_lock(&lo->lo_ctl_mutex); err = loop_set_status_compat( lo, (const struct compat_loop_info __user *) arg); - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); break; case LOOP_GET_STATUS: - mutex_lock(&loop_ctl_mutex); + mutex_lock(&lo->lo_ctl_mutex); err = loop_get_status_compat( lo, (struct compat_loop_info __user *) arg); - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); break; case LOOP_SET_CAPACITY: case LOOP_CLR_FD: @@ -1650,7 +1649,7 @@ static void __lo_release(struct loop_device *lo) if (atomic_dec_return(&lo->lo_refcnt)) return; - mutex_lock(&loop_ctl_mutex); + mutex_lock(&lo->lo_ctl_mutex); if (lo->lo_flags & LO_FLAGS_AUTOCLEAR) { /* * In autoclear mode, stop the loop thread @@ -1667,7 +1666,7 @@ static void __lo_release(struct loop_device *lo) loop_flush(lo); } - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); } static void lo_release(struct gendisk *disk, fmode_t mode) @@ -1713,10 +1712,10 @@ static int unregister_transfer_cb(int id, void *ptr, void *data) struct loop_device *lo = ptr; struct loop_func_table *xfer = data; - mutex_lock(&loop_ctl_mutex); + mutex_lock(&lo->lo_ctl_mutex); if (lo->lo_encryption == xfer) loop_release_xfer(lo); - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); return 0; } @@ -1879,6 +1878,7 @@ static int loop_add(struct loop_device **l, int i) if (!part_shift) disk->flags |= GENHD_FL_NO_PART_SCAN; disk->flags |= GENHD_FL_EXT_DEVT; + mutex_init(&lo->lo_ctl_mutex); atomic_set(&lo->lo_refcnt, 0); lo->lo_number = i; spin_lock_init(&lo->lo_lock); @@ -1991,19 +1991,19 @@ static long loop_control_ioctl(struct file *file, unsigned int cmd, ret = loop_lookup(&lo, parm); if (ret < 0) break; - mutex_lock(&loop_ctl_mutex); + mutex_lock(&lo->lo_ctl_mutex); if (lo->lo_state != Lo_unbound) { ret = -EBUSY; - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); break; } if (atomic_read(&lo->lo_refcnt) > 0) { ret = -EBUSY; - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); break; } lo->lo_disk->private_data = NULL; - mutex_unlock(&loop_ctl_mutex); + mutex_unlock(&lo->lo_ctl_mutex); idr_remove(&loop_index_idr, lo->lo_number); loop_remove(lo); break; diff --git a/drivers/block/loop.h b/drivers/block/loop.h index a923e74495ce..60f0fd2c0c65 100644 --- a/drivers/block/loop.h +++ b/drivers/block/loop.h @@ -55,6 +55,7 @@ struct loop_device { spinlock_t lo_lock; int lo_state; + struct mutex lo_ctl_mutex; struct kthread_worker worker; struct task_struct *worker_task; bool use_dio; diff --git a/drivers/block/xsysace.c b/drivers/block/xsysace.c index c4328d9d9981..f838119d12b2 100644 --- a/drivers/block/xsysace.c +++ b/drivers/block/xsysace.c @@ -1062,6 +1062,8 @@ static int ace_setup(struct ace_device *ace) return 0; err_read: + /* prevent double queue cleanup */ + ace->gd->queue = NULL; put_disk(ace->gd); err_alloc_disk: blk_cleanup_queue(ace->queue); diff --git a/drivers/gpu/ipu-v3/ipu-dp.c b/drivers/gpu/ipu-v3/ipu-dp.c index 98686edbcdbb..33de3a1bac49 100644 --- a/drivers/gpu/ipu-v3/ipu-dp.c +++ b/drivers/gpu/ipu-v3/ipu-dp.c @@ -195,7 +195,8 @@ int ipu_dp_setup_channel(struct ipu_dp *dp, ipu_dp_csc_init(flow, flow->foreground.in_cs, flow->out_cs, DP_COM_CONF_CSC_DEF_BOTH); } else { - if (flow->foreground.in_cs == flow->out_cs) + if (flow->foreground.in_cs == IPUV3_COLORSPACE_UNKNOWN || + flow->foreground.in_cs == flow->out_cs) /* * foreground identical to output, apply color * conversion on background @@ -261,6 +262,8 @@ void ipu_dp_disable_channel(struct ipu_dp *dp) struct ipu_dp_priv *priv = flow->priv; u32 reg, csc; + dp->in_cs = IPUV3_COLORSPACE_UNKNOWN; + if (!dp->foreground) return; @@ -268,8 +271,9 @@ void ipu_dp_disable_channel(struct ipu_dp *dp) reg = readl(flow->base + DP_COM_CONF); csc = reg & DP_COM_CONF_CSC_DEF_MASK; - if (csc == DP_COM_CONF_CSC_DEF_FG) - reg &= ~DP_COM_CONF_CSC_DEF_MASK; + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + if (csc == DP_COM_CONF_CSC_DEF_BOTH || csc == DP_COM_CONF_CSC_DEF_BG) + reg |= DP_COM_CONF_CSC_DEF_BG; reg &= ~DP_COM_CONF_FG_EN; writel(reg, flow->base + DP_COM_CONF); @@ -350,6 +354,8 @@ int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base) mutex_init(&priv->mutex); for (i = 0; i < IPUV3_NUM_FLOWS; i++) { + priv->flow[i].background.in_cs = IPUV3_COLORSPACE_UNKNOWN; + priv->flow[i].foreground.in_cs = IPUV3_COLORSPACE_UNKNOWN; priv->flow[i].foreground.foreground = true; priv->flow[i].base = priv->base + ipu_dp_flow_base[i]; priv->flow[i].priv = priv; diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index d7179dd3c9ef..3cafa1d28fed 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -1058,10 +1058,15 @@ static int hid_debug_rdesc_show(struct seq_file *f, void *p) seq_printf(f, "\n\n"); /* dump parsed data and input mappings */ + if (down_interruptible(&hdev->driver_input_lock)) + return 0; + hid_dump_device(hdev, f); seq_printf(f, "\n"); hid_dump_input_mapping(hdev, f); + up(&hdev->driver_input_lock); + return 0; } diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 8d74e691ac90..ee3c66c02043 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -783,6 +783,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x074: map_key_clear(KEY_BRIGHTNESS_MAX); break; case 0x075: map_key_clear(KEY_BRIGHTNESS_AUTO); break; + case 0x079: map_key_clear(KEY_KBDILLUMUP); break; + case 0x07a: map_key_clear(KEY_KBDILLUMDOWN); break; + case 0x07c: map_key_clear(KEY_KBDILLUMTOGGLE); break; + case 0x082: map_key_clear(KEY_VIDEO_NEXT); break; case 0x083: map_key_clear(KEY_LAST); break; case 0x084: map_key_clear(KEY_ENTER); break; @@ -913,6 +917,8 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x2cb: map_key_clear(KEY_KBDINPUTASSIST_ACCEPT); break; case 0x2cc: map_key_clear(KEY_KBDINPUTASSIST_CANCEL); break; + case 0x29f: map_key_clear(KEY_SCALE); break; + default: map_key_clear(KEY_UNKNOWN); } break; diff --git a/drivers/hwtracing/intel_th/gth.c b/drivers/hwtracing/intel_th/gth.c index eb43943cdf07..189eb6269971 100644 --- a/drivers/hwtracing/intel_th/gth.c +++ b/drivers/hwtracing/intel_th/gth.c @@ -597,7 +597,7 @@ static void intel_th_gth_unassign(struct intel_th_device *thdev, othdev->output.port = -1; othdev->output.active = false; gth->output[port].output = NULL; - for (master = 0; master < TH_CONFIGURABLE_MASTERS; master++) + for (master = 0; master <= TH_CONFIGURABLE_MASTERS; master++) if (gth->master[master] == port) gth->master[master] = -1; spin_unlock(>h->gth_lock); diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index 475c5a74f2d1..6398e86a272b 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -1299,7 +1299,7 @@ static int xadc_remove(struct platform_device *pdev) } free_irq(irq, indio_dev); clk_disable_unprepare(xadc->clk); - cancel_delayed_work(&xadc->zynq_unmask_work); + cancel_delayed_work_sync(&xadc->zynq_unmask_work); kfree(xadc->data); kfree(indio_dev->channels); diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c index 9adf13a5864a..57143365e945 100644 --- a/drivers/input/keyboard/snvs_pwrkey.c +++ b/drivers/input/keyboard/snvs_pwrkey.c @@ -156,6 +156,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) return error; } + pdata->input = input; + platform_set_drvdata(pdev, pdata); + error = devm_request_irq(&pdev->dev, pdata->irq, imx_snvs_pwrkey_interrupt, 0, pdev->name, pdev); @@ -172,9 +175,6 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) return error; } - pdata->input = input; - platform_set_drvdata(pdev, pdata); - device_init_wakeup(&pdev->dev, pdata->wakeup); return 0; diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index 94f1bf772ec9..db85cc5791dc 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -295,7 +295,7 @@ static void iommu_write_l2(struct amd_iommu *iommu, u8 address, u32 val) static void iommu_set_exclusion_range(struct amd_iommu *iommu) { u64 start = iommu->exclusion_start & PAGE_MASK; - u64 limit = (start + iommu->exclusion_length) & PAGE_MASK; + u64 limit = (start + iommu->exclusion_length - 1) & PAGE_MASK; u64 entry; if (!iommu->exclusion_start) diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index c6133d19ae28..43e49105d97c 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -3897,26 +3897,15 @@ static void handle_parity_checks6(struct r5conf *conf, struct stripe_head *sh, case check_state_check_result: sh->check_state = check_state_idle; + if (s->failed > 1) + break; /* handle a successful check operation, if parity is correct * we are done. Otherwise update the mismatch count and repair * parity if !MD_RECOVERY_CHECK */ if (sh->ops.zero_sum_result == 0) { - /* both parities are correct */ - if (!s->failed) - set_bit(STRIPE_INSYNC, &sh->state); - else { - /* in contrast to the raid5 case we can validate - * parity, but still have a failure to write - * back - */ - sh->check_state = check_state_compute_result; - /* Returning at this point means that we may go - * off and bring p and/or q uptodate again so - * we make sure to check zero_sum_result again - * to verify if p or q need writeback - */ - } + /* Any parity checked was correct */ + set_bit(STRIPE_INSYNC, &sh->state); } else { atomic64_add(STRIPE_SECTORS, &conf->mddev->resync_mismatches); if (test_bit(MD_RECOVERY_CHECK, &conf->mddev->recovery)) diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index e1b5dc84c14e..24a0c21a3d8d 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -155,10 +155,10 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)"); #define REG_GFIX 0x69 /* Fix gain control */ #define REG_DBLV 0x6b /* PLL control an debugging */ -#define DBLV_BYPASS 0x00 /* Bypass PLL */ -#define DBLV_X4 0x01 /* clock x4 */ -#define DBLV_X6 0x10 /* clock x6 */ -#define DBLV_X8 0x11 /* clock x8 */ +#define DBLV_BYPASS 0x0a /* Bypass PLL */ +#define DBLV_X4 0x4a /* clock x4 */ +#define DBLV_X6 0x8a /* clock x6 */ +#define DBLV_X8 0xca /* clock x8 */ #define REG_REG76 0x76 /* OV's name */ #define R76_BLKPCOR 0x80 /* Black pixel correction enable */ @@ -833,7 +833,7 @@ static int ov7675_set_framerate(struct v4l2_subdev *sd, if (ret < 0) return ret; - return ov7670_write(sd, REG_DBLV, DBLV_X4); + return 0; } static void ov7670_get_framerate_legacy(struct v4l2_subdev *sd, @@ -1578,11 +1578,7 @@ static int ov7670_probe(struct i2c_client *client, if (config->clock_speed) info->clock_speed = config->clock_speed; - /* - * It should be allowed for ov7670 too when it is migrated to - * the new frame rate formula. - */ - if (config->pll_bypass && id->driver_data != MODEL_OV7670) + if (config->pll_bypass) info->pll_bypass = true; if (config->pclk_hb_disable) 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/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 66560a8fcfa2..1022e80aaf97 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -1066,13 +1066,6 @@ static int bond_option_arp_validate_set(struct bonding *bond, { netdev_info(bond->dev, "Setting arp_validate to %s (%llu)\n", newval->string, newval->value); - - if (bond->dev->flags & IFF_UP) { - if (!newval->value) - bond->recv_probe = NULL; - else if (bond->params.arp_interval) - bond->recv_probe = bond_arp_rcv; - } bond->params.arp_validate = newval->value; return 0; diff --git a/drivers/net/bonding/bond_sysfs_slave.c b/drivers/net/bonding/bond_sysfs_slave.c index 7d16c51e6913..641a532b67cb 100644 --- a/drivers/net/bonding/bond_sysfs_slave.c +++ b/drivers/net/bonding/bond_sysfs_slave.c @@ -55,7 +55,9 @@ static SLAVE_ATTR_RO(link_failure_count); static ssize_t perm_hwaddr_show(struct slave *slave, char *buf) { - return sprintf(buf, "%pM\n", slave->perm_hwaddr); + return sprintf(buf, "%*phC\n", + slave->dev->addr_len, + slave->perm_hwaddr); } static SLAVE_ATTR_RO(perm_hwaddr); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 00bd7be85679..d9ab970dcbe9 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -4957,8 +4957,15 @@ static int bnxt_cfg_rx_mode(struct bnxt *bp) skip_uc: rc = bnxt_hwrm_cfa_l2_set_rx_mask(bp, 0); + if (rc && vnic->mc_list_count) { + netdev_info(bp->dev, "Failed setting MC filters rc: %d, turning on ALL_MCAST mode\n", + rc); + vnic->rx_mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST; + vnic->mc_list_count = 0; + rc = bnxt_hwrm_cfa_l2_set_rx_mask(bp, 0); + } if (rc) - netdev_err(bp->dev, "HWRM cfa l2 rx mask failure rc: %x\n", + netdev_err(bp->dev, "HWRM cfa l2 rx mask failure rc: %d\n", rc); return rc; diff --git a/drivers/net/ethernet/freescale/ucc_geth_ethtool.c b/drivers/net/ethernet/freescale/ucc_geth_ethtool.c index 89714f5e0dfc..c8b9a73d6b1b 100644 --- a/drivers/net/ethernet/freescale/ucc_geth_ethtool.c +++ b/drivers/net/ethernet/freescale/ucc_geth_ethtool.c @@ -253,14 +253,12 @@ uec_set_ringparam(struct net_device *netdev, return -EINVAL; } + if (netif_running(netdev)) + return -EBUSY; + ug_info->bdRingLenRx[queue] = ring->rx_pending; ug_info->bdRingLenTx[queue] = ring->tx_pending; - if (netif_running(netdev)) { - /* FIXME: restart automatically */ - netdev_info(netdev, "Please re-open the interface\n"); - } - return ret; } diff --git a/drivers/net/ethernet/hisilicon/hns/hnae.c b/drivers/net/ethernet/hisilicon/hns/hnae.c index b3645297477e..3ce41efe8a94 100644 --- a/drivers/net/ethernet/hisilicon/hns/hnae.c +++ b/drivers/net/ethernet/hisilicon/hns/hnae.c @@ -144,7 +144,6 @@ out_buffer_fail: /* free desc along with its attached buffer */ static void hnae_free_desc(struct hnae_ring *ring) { - hnae_free_buffers(ring); dma_unmap_single(ring_to_dev(ring), ring->desc_dma_addr, ring->desc_num * sizeof(ring->desc[0]), ring_to_dma_dir(ring)); @@ -177,6 +176,9 @@ static int hnae_alloc_desc(struct hnae_ring *ring) /* fini ring, also free the buffer for the ring */ static void hnae_fini_ring(struct hnae_ring *ring) { + if (is_rx_ring(ring)) + hnae_free_buffers(ring); + hnae_free_desc(ring); kfree(ring->desc_cb); ring->desc_cb = NULL; diff --git a/drivers/net/ethernet/hisilicon/hns/hns_enet.c b/drivers/net/ethernet/hisilicon/hns/hns_enet.c index 7948c5a9e5fc..bd5c22986343 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_enet.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_enet.c @@ -28,9 +28,6 @@ #define SERVICE_TIMER_HZ (1 * HZ) -#define NIC_TX_CLEAN_MAX_NUM 256 -#define NIC_RX_CLEAN_MAX_NUM 64 - #define RCB_IRQ_NOT_INITED 0 #define RCB_IRQ_INITED 1 @@ -1406,7 +1403,7 @@ static int hns_nic_init_ring_data(struct hns_nic_priv *priv) rd->fini_process = hns_nic_tx_fini_pro; netif_napi_add(priv->netdev, &rd->napi, - hns_nic_common_poll, NIC_TX_CLEAN_MAX_NUM); + hns_nic_common_poll, NAPI_POLL_WEIGHT); rd->ring->irq_init_flag = RCB_IRQ_NOT_INITED; } for (i = h->q_num; i < h->q_num * 2; i++) { @@ -1418,7 +1415,7 @@ static int hns_nic_init_ring_data(struct hns_nic_priv *priv) rd->fini_process = hns_nic_rx_fini_pro; netif_napi_add(priv->netdev, &rd->napi, - hns_nic_common_poll, NIC_RX_CLEAN_MAX_NUM); + hns_nic_common_poll, NAPI_POLL_WEIGHT); rd->ring->irq_init_flag = RCB_IRQ_NOT_INITED; } diff --git a/drivers/net/ethernet/ibm/ehea/ehea_main.c b/drivers/net/ethernet/ibm/ehea/ehea_main.c index 2a0dc127df3f..1a56de06b014 100644 --- a/drivers/net/ethernet/ibm/ehea/ehea_main.c +++ b/drivers/net/ethernet/ibm/ehea/ehea_main.c @@ -3183,6 +3183,7 @@ static ssize_t ehea_probe_port(struct device *dev, if (ehea_add_adapter_mr(adapter)) { pr_err("creating MR failed\n"); + of_node_put(eth_dn); return -EIO; } diff --git a/drivers/net/ethernet/intel/igb/e1000_defines.h b/drivers/net/ethernet/intel/igb/e1000_defines.h index b1915043bc0c..7b9fb71137da 100644 --- a/drivers/net/ethernet/intel/igb/e1000_defines.h +++ b/drivers/net/ethernet/intel/igb/e1000_defines.h @@ -193,6 +193,8 @@ /* enable link status from external LINK_0 and LINK_1 pins */ #define E1000_CTRL_SWDPIN0 0x00040000 /* SWDPIN 0 value */ #define E1000_CTRL_SWDPIN1 0x00080000 /* SWDPIN 1 value */ +#define E1000_CTRL_ADVD3WUC 0x00100000 /* D3 WUC */ +#define E1000_CTRL_EN_PHY_PWR_MGMT 0x00200000 /* PHY PM enable */ #define E1000_CTRL_SDP0_DIR 0x00400000 /* SDP0 Data direction */ #define E1000_CTRL_SDP1_DIR 0x00800000 /* SDP1 Data direction */ #define E1000_CTRL_RST 0x04000000 /* Global reset */ diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index c1796aa2dde5..70ed5e5c3514 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -7325,9 +7325,7 @@ static int __igb_shutdown(struct pci_dev *pdev, bool *enable_wake, struct e1000_hw *hw = &adapter->hw; u32 ctrl, rctl, status; u32 wufc = runtime ? E1000_WUFC_LNKC : adapter->wol; -#ifdef CONFIG_PM - int retval = 0; -#endif + bool wake; rtnl_lock(); netif_device_detach(netdev); @@ -7338,14 +7336,6 @@ static int __igb_shutdown(struct pci_dev *pdev, bool *enable_wake, igb_clear_interrupt_scheme(adapter); rtnl_unlock(); -#ifdef CONFIG_PM - if (!runtime) { - retval = pci_save_state(pdev); - if (retval) - return retval; - } -#endif - status = rd32(E1000_STATUS); if (status & E1000_STATUS_LU) wufc &= ~E1000_WUFC_LNKC; @@ -7362,10 +7352,6 @@ static int __igb_shutdown(struct pci_dev *pdev, bool *enable_wake, } ctrl = rd32(E1000_CTRL); - /* advertise wake from D3Cold */ - #define E1000_CTRL_ADVD3WUC 0x00100000 - /* phy power management enable */ - #define E1000_CTRL_EN_PHY_PWR_MGMT 0x00200000 ctrl |= E1000_CTRL_ADVD3WUC; wr32(E1000_CTRL, ctrl); @@ -7379,12 +7365,15 @@ static int __igb_shutdown(struct pci_dev *pdev, bool *enable_wake, wr32(E1000_WUFC, 0); } - *enable_wake = wufc || adapter->en_mng_pt; - if (!*enable_wake) + wake = wufc || adapter->en_mng_pt; + if (!wake) igb_power_down_link(adapter); else igb_power_up_link(adapter); + if (enable_wake) + *enable_wake = wake; + /* Release control of h/w to f/w. If f/w is AMT enabled, this * would have already happened in close and is redundant. */ @@ -7399,22 +7388,7 @@ static int __igb_shutdown(struct pci_dev *pdev, bool *enable_wake, #ifdef CONFIG_PM_SLEEP static int igb_suspend(struct device *dev) { - int retval; - bool wake; - struct pci_dev *pdev = to_pci_dev(dev); - - retval = __igb_shutdown(pdev, &wake, 0); - if (retval) - return retval; - - if (wake) { - pci_prepare_to_sleep(pdev); - } else { - pci_wake_from_d3(pdev, false); - pci_set_power_state(pdev, PCI_D3hot); - } - - return 0; + return __igb_shutdown(to_pci_dev(dev), NULL, 0); } #endif /* CONFIG_PM_SLEEP */ @@ -7483,22 +7457,7 @@ static int igb_runtime_idle(struct device *dev) static int igb_runtime_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - int retval; - bool wake; - - retval = __igb_shutdown(pdev, &wake, 1); - if (retval) - return retval; - - if (wake) { - pci_prepare_to_sleep(pdev); - } else { - pci_wake_from_d3(pdev, false); - pci_set_power_state(pdev, PCI_D3hot); - } - - return 0; + return __igb_shutdown(to_pci_dev(dev), NULL, 1); } static int igb_runtime_resume(struct device *dev) diff --git a/drivers/net/ethernet/micrel/ks8851.c b/drivers/net/ethernet/micrel/ks8851.c index 1edc973df4c4..7377dca6eb57 100644 --- a/drivers/net/ethernet/micrel/ks8851.c +++ b/drivers/net/ethernet/micrel/ks8851.c @@ -547,9 +547,8 @@ static void ks8851_rx_pkts(struct ks8851_net *ks) /* set dma read address */ ks8851_wrreg16(ks, KS_RXFDPR, RXFDPR_RXFPAI | 0x00); - /* start the packet dma process, and set auto-dequeue rx */ - ks8851_wrreg16(ks, KS_RXQCR, - ks->rc_rxqcr | RXQCR_SDA | RXQCR_ADRFE); + /* start DMA access */ + ks8851_wrreg16(ks, KS_RXQCR, ks->rc_rxqcr | RXQCR_SDA); if (rxlen > 4) { unsigned int rxalign; @@ -580,7 +579,8 @@ static void ks8851_rx_pkts(struct ks8851_net *ks) } } - ks8851_wrreg16(ks, KS_RXQCR, ks->rc_rxqcr); + /* end DMA access and dequeue packet */ + ks8851_wrreg16(ks, KS_RXQCR, ks->rc_rxqcr | RXQCR_RRXEF); } } @@ -797,6 +797,15 @@ static void ks8851_tx_work(struct work_struct *work) static int ks8851_net_open(struct net_device *dev) { struct ks8851_net *ks = netdev_priv(dev); + int ret; + + ret = request_threaded_irq(dev->irq, NULL, ks8851_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev->name, ks); + if (ret < 0) { + netdev_err(dev, "failed to get irq\n"); + return ret; + } /* lock the card, even if we may not actually be doing anything * else at the moment */ @@ -861,6 +870,7 @@ static int ks8851_net_open(struct net_device *dev) netif_dbg(ks, ifup, ks->netdev, "network device up\n"); mutex_unlock(&ks->lock); + mii_check_link(&ks->mii); return 0; } @@ -911,6 +921,8 @@ static int ks8851_net_stop(struct net_device *dev) dev_kfree_skb(txb); } + free_irq(dev->irq, ks); + return 0; } @@ -1516,6 +1528,7 @@ static int ks8851_probe(struct spi_device *spi) spi_set_drvdata(spi, ks); + netif_carrier_off(ks->netdev); ndev->if_port = IF_PORT_100BASET; ndev->netdev_ops = &ks8851_netdev_ops; ndev->irq = spi->irq; @@ -1542,14 +1555,6 @@ static int ks8851_probe(struct spi_device *spi) ks8851_read_selftest(ks); ks8851_init_mac(ks); - ret = request_threaded_irq(spi->irq, NULL, ks8851_irq, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - ndev->name, ks); - if (ret < 0) { - dev_err(&spi->dev, "failed to get irq\n"); - goto err_irq; - } - ret = register_netdev(ndev); if (ret) { dev_err(&spi->dev, "failed to register network device\n"); @@ -1562,14 +1567,10 @@ static int ks8851_probe(struct spi_device *spi) return 0; - err_netdev: - free_irq(ndev->irq, ks); - -err_irq: +err_id: if (gpio_is_valid(gpio)) gpio_set_value(gpio, 0); -err_id: regulator_disable(ks->vdd_reg); err_reg: regulator_disable(ks->vdd_io); @@ -1587,7 +1588,6 @@ static int ks8851_remove(struct spi_device *spi) dev_info(&spi->dev, "remove\n"); unregister_netdev(priv->netdev); - free_irq(spi->irq, priv); if (gpio_is_valid(priv->gpio)) gpio_set_value(priv->gpio, 0); regulator_disable(priv->vdd_reg); diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c index 0a2318cad34d..63ebc491057b 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c @@ -1038,6 +1038,8 @@ int qlcnic_do_lb_test(struct qlcnic_adapter *adapter, u8 mode) for (i = 0; i < QLCNIC_NUM_ILB_PKT; i++) { skb = netdev_alloc_skb(adapter->netdev, QLCNIC_ILB_PKT_SIZE); + if (!skb) + break; qlcnic_create_loopback_buff(skb->data, adapter->mac_addr); skb_put(skb, QLCNIC_ILB_PKT_SIZE); adapter->ahw->diag_cnt = 0; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 059113dce6e0..f4d6512f066c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1792,8 +1792,6 @@ static int stmmac_open(struct net_device *dev) struct stmmac_priv *priv = netdev_priv(dev); int ret; - stmmac_check_ether_addr(priv); - if (priv->pcs != STMMAC_PCS_RGMII && priv->pcs != STMMAC_PCS_TBI && priv->pcs != STMMAC_PCS_RTBI) { ret = stmmac_init_phy(dev); @@ -2929,6 +2927,8 @@ int stmmac_dvr_probe(struct device *device, if (ret) goto error_hw_init; + stmmac_check_ether_addr(priv); + ndev->netdev_ops = &stmmac_netdev_ops; ndev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | diff --git a/drivers/net/ethernet/ti/netcp_ethss.c b/drivers/net/ethernet/ti/netcp_ethss.c index 4e70e7586a09..a5732edc8437 100644 --- a/drivers/net/ethernet/ti/netcp_ethss.c +++ b/drivers/net/ethernet/ti/netcp_ethss.c @@ -3122,12 +3122,16 @@ static int gbe_probe(struct netcp_device *netcp_device, struct device *dev, ret = netcp_txpipe_init(&gbe_dev->tx_pipe, netcp_device, gbe_dev->dma_chan_name, gbe_dev->tx_queue_id); - if (ret) + if (ret) { + of_node_put(interfaces); return ret; + } ret = netcp_txpipe_open(&gbe_dev->tx_pipe); - if (ret) + if (ret) { + of_node_put(interfaces); return ret; + } /* Create network interfaces */ INIT_LIST_HEAD(&gbe_dev->gbe_intf_head); diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 4684644703cc..58ba579793f8 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1595,12 +1595,14 @@ static int axienet_probe(struct platform_device *pdev) ret = of_address_to_resource(np, 0, &dmares); if (ret) { dev_err(&pdev->dev, "unable to get DMA resource\n"); + of_node_put(np); goto free_netdev; } lp->dma_regs = devm_ioremap_resource(&pdev->dev, &dmares); if (IS_ERR(lp->dma_regs)) { dev_err(&pdev->dev, "could not map DMA regs\n"); ret = PTR_ERR(lp->dma_regs); + of_node_put(np); goto free_netdev; } lp->rx_irq = irq_of_parse_and_map(np, 1); diff --git a/drivers/net/slip/slhc.c b/drivers/net/slip/slhc.c index cfd81eb1b532..ddceed3c5a4a 100644 --- a/drivers/net/slip/slhc.c +++ b/drivers/net/slip/slhc.c @@ -153,7 +153,7 @@ out_fail: void slhc_free(struct slcompress *comp) { - if ( comp == NULLSLCOMPR ) + if ( IS_ERR_OR_NULL(comp) ) return; if ( comp->tstate != NULLSLSTATE ) diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c index 267a90423154..7b3ef6dc45a4 100644 --- a/drivers/net/team/team.c +++ b/drivers/net/team/team.c @@ -1136,6 +1136,12 @@ static int team_port_add(struct team *team, struct net_device *port_dev) return -EINVAL; } + if (netdev_has_upper_dev(dev, port_dev)) { + netdev_err(dev, "Device %s is already an upper device of the team interface\n", + portname); + return -EBUSY; + } + if (port_dev->features & NETIF_F_VLAN_CHALLENGED && vlan_uses_dev(dev)) { netdev_err(dev, "Device %s is VLAN challenged and team device has VLAN set up\n", diff --git a/drivers/net/usb/ipheth.c b/drivers/net/usb/ipheth.c index f1f8227e7342..01f95d192d25 100644 --- a/drivers/net/usb/ipheth.c +++ b/drivers/net/usb/ipheth.c @@ -148,6 +148,7 @@ struct ipheth_device { u8 bulk_in; u8 bulk_out; struct delayed_work carrier_work; + bool confirmed_pairing; }; static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags); @@ -259,7 +260,7 @@ static void ipheth_rcvbulk_callback(struct urb *urb) dev->net->stats.rx_packets++; dev->net->stats.rx_bytes += len; - + dev->confirmed_pairing = true; netif_rx(skb); ipheth_rx_submit(dev, GFP_ATOMIC); } @@ -280,14 +281,24 @@ static void ipheth_sndbulk_callback(struct urb *urb) dev_err(&dev->intf->dev, "%s: urb status: %d\n", __func__, status); - netif_wake_queue(dev->net); + if (status == 0) + netif_wake_queue(dev->net); + else + // on URB error, trigger immediate poll + schedule_delayed_work(&dev->carrier_work, 0); } static int ipheth_carrier_set(struct ipheth_device *dev) { - struct usb_device *udev = dev->udev; + struct usb_device *udev; int retval; + if (!dev) + return 0; + if (!dev->confirmed_pairing) + return 0; + + udev = dev->udev; retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, IPHETH_CTRL_ENDP), IPHETH_CMD_CARRIER_CHECK, /* request */ @@ -302,11 +313,14 @@ static int ipheth_carrier_set(struct ipheth_device *dev) return retval; } - if (dev->ctrl_buf[0] == IPHETH_CARRIER_ON) + if (dev->ctrl_buf[0] == IPHETH_CARRIER_ON) { netif_carrier_on(dev->net); - else + if (dev->tx_urb->status != -EINPROGRESS) + netif_wake_queue(dev->net); + } else { netif_carrier_off(dev->net); - + netif_stop_queue(dev->net); + } return 0; } @@ -386,7 +400,6 @@ static int ipheth_open(struct net_device *net) return retval; schedule_delayed_work(&dev->carrier_work, IPHETH_CARRIER_CHECK_TIMEOUT); - netif_start_queue(net); return retval; } @@ -489,7 +502,7 @@ static int ipheth_probe(struct usb_interface *intf, dev->udev = udev; dev->net = netdev; dev->intf = intf; - + dev->confirmed_pairing = false; /* Set up endpoints */ hintf = usb_altnum_to_altsetting(intf, IPHETH_ALT_INTFNUM); if (hintf == NULL) { @@ -540,7 +553,9 @@ static int ipheth_probe(struct usb_interface *intf, retval = -EIO; goto err_register_netdev; } - + // carrier down and transmit queues stopped until packet from device + netif_carrier_off(netdev); + netif_tx_stop_all_queues(netdev); dev_info(&intf->dev, "Apple iPhone USB Ethernet device attached\n"); return 0; diff --git a/drivers/net/wireless/cw1200/scan.c b/drivers/net/wireless/cw1200/scan.c index 9f1037e7e55c..2ce0193614f2 100644 --- a/drivers/net/wireless/cw1200/scan.c +++ b/drivers/net/wireless/cw1200/scan.c @@ -84,8 +84,11 @@ int cw1200_hw_scan(struct ieee80211_hw *hw, frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0, req->ie_len); - if (!frame.skb) + if (!frame.skb) { + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); return -ENOMEM; + } if (req->ie_len) memcpy(skb_put(frame.skb, req->ie_len), req->ie, req->ie_len); diff --git a/drivers/nvdimm/btt_devs.c b/drivers/nvdimm/btt_devs.c index cb477518dd0e..4c129450495d 100644 --- a/drivers/nvdimm/btt_devs.c +++ b/drivers/nvdimm/btt_devs.c @@ -170,14 +170,15 @@ static struct device *__nd_btt_create(struct nd_region *nd_region, return NULL; nd_btt->id = ida_simple_get(&nd_region->btt_ida, 0, 0, GFP_KERNEL); - if (nd_btt->id < 0) { - kfree(nd_btt); - return NULL; - } + if (nd_btt->id < 0) + goto out_nd_btt; nd_btt->lbasize = lbasize; - if (uuid) + if (uuid) { uuid = kmemdup(uuid, 16, GFP_KERNEL); + if (!uuid) + goto out_put_id; + } nd_btt->uuid = uuid; dev = &nd_btt->dev; dev_set_name(dev, "btt%d.%d", nd_region->id, nd_btt->id); @@ -192,6 +193,13 @@ static struct device *__nd_btt_create(struct nd_region *nd_region, return NULL; } return dev; + +out_put_id: + ida_simple_remove(&nd_region->btt_ida, nd_btt->id); + +out_nd_btt: + kfree(nd_btt); + return NULL; } struct device *nd_btt_create(struct nd_region *nd_region) 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/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index f73c29558cd3..c54ff94c491d 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -4394,14 +4394,16 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) } return AE_OK; } + + case ACPI_RESOURCE_TYPE_END_TAG: + return AE_OK; + default: dprintk("Resource %d isn't an IRQ nor an IO port\n", resource->type); + return AE_CTRL_TERMINATE; - case ACPI_RESOURCE_TYPE_END_TAG: - return AE_OK; } - return AE_CTRL_TERMINATE; } static int sony_pic_possible_resources(struct acpi_device *device) diff --git a/drivers/rtc/rtc-da9063.c b/drivers/rtc/rtc-da9063.c index d6c853bbfa9f..e93beecd5010 100644 --- a/drivers/rtc/rtc-da9063.c +++ b/drivers/rtc/rtc-da9063.c @@ -491,6 +491,13 @@ static int da9063_rtc_probe(struct platform_device *pdev) da9063_data_to_tm(data, &rtc->alarm_time, rtc); rtc->rtc_sync = false; + /* + * TODO: some models have alarms on a minute boundary but still support + * real hardware interrupts. Add this once the core supports it. + */ + if (config->rtc_data_start != RTC_SEC) + rtc->rtc_dev->uie_unsupported = 1; + irq_alarm = platform_get_irq_byname(pdev, "ALARM"); ret = devm_request_threaded_irq(&pdev->dev, irq_alarm, NULL, da9063_alarm_event, diff --git a/drivers/rtc/rtc-sh.c b/drivers/rtc/rtc-sh.c index 2b81dd4baf17..104c854d6a8a 100644 --- a/drivers/rtc/rtc-sh.c +++ b/drivers/rtc/rtc-sh.c @@ -455,7 +455,7 @@ static int sh_rtc_set_time(struct device *dev, struct rtc_time *tm) static inline int sh_rtc_read_alarm_value(struct sh_rtc *rtc, int reg_off) { unsigned int byte; - int value = 0xff; /* return 0xff for ignored values */ + int value = -1; /* return -1 for ignored values */ byte = readb(rtc->regbase + reg_off); if (byte & AR_ENB) { diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 80a43074c2f9..c530610f61ac 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -2066,14 +2066,14 @@ static int dasd_eckd_end_analysis(struct dasd_block *block) blk_per_trk = recs_per_track(&private->rdc_data, 0, block->bp_block); raw: - block->blocks = (private->real_cyl * + block->blocks = ((unsigned long) private->real_cyl * private->rdc_data.trk_per_cyl * blk_per_trk); dev_info(&device->cdev->dev, - "DASD with %d KB/block, %d KB total size, %d KB/track, " + "DASD with %u KB/block, %lu KB total size, %u KB/track, " "%s\n", (block->bp_block >> 10), - ((private->real_cyl * + (((unsigned long) private->real_cyl * private->rdc_data.trk_per_cyl * blk_per_trk * (block->bp_block >> 9)) >> 1), ((blk_per_trk * block->bp_block) >> 10), diff --git a/drivers/s390/char/con3270.c b/drivers/s390/char/con3270.c index bae98521c808..3e5a7912044f 100644 --- a/drivers/s390/char/con3270.c +++ b/drivers/s390/char/con3270.c @@ -627,7 +627,7 @@ con3270_init(void) (void (*)(unsigned long)) con3270_read_tasklet, (unsigned long) condev->read); - raw3270_add_view(&condev->view, &con3270_fn, 1); + raw3270_add_view(&condev->view, &con3270_fn, 1, RAW3270_VIEW_LOCK_IRQ); INIT_LIST_HEAD(&condev->freemem); for (i = 0; i < CON3270_STRING_PAGES; i++) { diff --git a/drivers/s390/char/fs3270.c b/drivers/s390/char/fs3270.c index 71e974738014..f0c86bcbe316 100644 --- a/drivers/s390/char/fs3270.c +++ b/drivers/s390/char/fs3270.c @@ -463,7 +463,8 @@ fs3270_open(struct inode *inode, struct file *filp) init_waitqueue_head(&fp->wait); fp->fs_pid = get_pid(task_pid(current)); - rc = raw3270_add_view(&fp->view, &fs3270_fn, minor); + rc = raw3270_add_view(&fp->view, &fs3270_fn, minor, + RAW3270_VIEW_LOCK_BH); if (rc) { fs3270_free_view(&fp->view); goto out; diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c index 220acb4cbee5..9c350e6d75bf 100644 --- a/drivers/s390/char/raw3270.c +++ b/drivers/s390/char/raw3270.c @@ -956,7 +956,7 @@ raw3270_deactivate_view(struct raw3270_view *view) * Add view to device with minor "minor". */ int -raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor) +raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor, int subclass) { unsigned long flags; struct raw3270 *rp; @@ -978,6 +978,7 @@ raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor) view->cols = rp->cols; view->ascebc = rp->ascebc; spin_lock_init(&view->lock); + lockdep_set_subclass(&view->lock, subclass); list_add(&view->list, &rp->view_list); rc = 0; spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags); diff --git a/drivers/s390/char/raw3270.h b/drivers/s390/char/raw3270.h index e1e41c2861fb..5ae54317857a 100644 --- a/drivers/s390/char/raw3270.h +++ b/drivers/s390/char/raw3270.h @@ -155,6 +155,8 @@ struct raw3270_fn { struct raw3270_view { struct list_head list; spinlock_t lock; +#define RAW3270_VIEW_LOCK_IRQ 0 +#define RAW3270_VIEW_LOCK_BH 1 atomic_t ref_count; struct raw3270 *dev; struct raw3270_fn *fn; @@ -163,7 +165,7 @@ struct raw3270_view { unsigned char *ascebc; /* ascii -> ebcdic table */ }; -int raw3270_add_view(struct raw3270_view *, struct raw3270_fn *, int); +int raw3270_add_view(struct raw3270_view *, struct raw3270_fn *, int, int); int raw3270_activate_view(struct raw3270_view *); void raw3270_del_view(struct raw3270_view *); void raw3270_deactivate_view(struct raw3270_view *); diff --git a/drivers/s390/char/tty3270.c b/drivers/s390/char/tty3270.c index e96fc7fd9498..ab95d24b991b 100644 --- a/drivers/s390/char/tty3270.c +++ b/drivers/s390/char/tty3270.c @@ -937,7 +937,8 @@ static int tty3270_install(struct tty_driver *driver, struct tty_struct *tty) return PTR_ERR(tp); rc = raw3270_add_view(&tp->view, &tty3270_fn, - tty->index + RAW3270_FIRSTMINOR); + tty->index + RAW3270_FIRSTMINOR, + RAW3270_VIEW_LOCK_BH); if (rc) { tty3270_free_view(tp); return rc; diff --git a/drivers/s390/net/ctcm_main.c b/drivers/s390/net/ctcm_main.c index 05c37d6d4afe..a31821d94677 100644 --- a/drivers/s390/net/ctcm_main.c +++ b/drivers/s390/net/ctcm_main.c @@ -1595,6 +1595,7 @@ static int ctcm_new_device(struct ccwgroup_device *cgdev) if (priv->channel[direction] == NULL) { if (direction == CTCM_WRITE) channel_free(priv->channel[CTCM_READ]); + result = -ENODEV; goto out_dev; } priv->channel[direction]->netdev = dev; diff --git a/drivers/s390/scsi/zfcp_fc.c b/drivers/s390/scsi/zfcp_fc.c index 237688af179b..f7630cf581cd 100644 --- a/drivers/s390/scsi/zfcp_fc.c +++ b/drivers/s390/scsi/zfcp_fc.c @@ -238,10 +238,6 @@ static void _zfcp_fc_incoming_rscn(struct zfcp_fsf_req *fsf_req, u32 range, list_for_each_entry(port, &adapter->port_list, list) { if ((port->d_id & range) == (ntoh24(page->rscn_fid) & range)) zfcp_fc_test_link(port); - if (!port->d_id) - zfcp_erp_port_reopen(port, - ZFCP_STATUS_COMMON_ERP_FAILED, - "fcrscn1"); } read_unlock_irqrestore(&adapter->port_list_lock, flags); } @@ -249,6 +245,7 @@ static void _zfcp_fc_incoming_rscn(struct zfcp_fsf_req *fsf_req, u32 range, static void zfcp_fc_incoming_rscn(struct zfcp_fsf_req *fsf_req) { struct fsf_status_read_buffer *status_buffer = (void *)fsf_req->data; + struct zfcp_adapter *adapter = fsf_req->adapter; struct fc_els_rscn *head; struct fc_els_rscn_page *page; u16 i; @@ -261,6 +258,22 @@ static void zfcp_fc_incoming_rscn(struct zfcp_fsf_req *fsf_req) /* see FC-FS */ no_entries = head->rscn_plen / sizeof(struct fc_els_rscn_page); + if (no_entries > 1) { + /* handle failed ports */ + unsigned long flags; + struct zfcp_port *port; + + read_lock_irqsave(&adapter->port_list_lock, flags); + list_for_each_entry(port, &adapter->port_list, list) { + if (port->d_id) + continue; + zfcp_erp_port_reopen(port, + ZFCP_STATUS_COMMON_ERP_FAILED, + "fcrscn1"); + } + read_unlock_irqrestore(&adapter->port_list_lock, flags); + } + for (i = 1; i < no_entries; i++) { /* skip head and start with 1st element */ page++; diff --git a/drivers/scsi/csiostor/csio_scsi.c b/drivers/scsi/csiostor/csio_scsi.c index c2a6f9f29427..ddbdaade654d 100644 --- a/drivers/scsi/csiostor/csio_scsi.c +++ b/drivers/scsi/csiostor/csio_scsi.c @@ -1713,8 +1713,11 @@ csio_scsi_err_handler(struct csio_hw *hw, struct csio_ioreq *req) } out: - if (req->nsge > 0) + if (req->nsge > 0) { scsi_dma_unmap(cmnd); + if (req->dcopy && (host_status == DID_OK)) + host_status = csio_scsi_copy_to_sgl(hw, req); + } cmnd->result = (((host_status) << 16) | scsi_status); cmnd->scsi_done(cmnd); diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 7be581f7c35d..1a6f65db615e 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -47,17 +47,16 @@ static void smp_task_timedout(unsigned long _task) unsigned long flags; spin_lock_irqsave(&task->task_state_lock, flags); - if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) + if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) { task->task_state_flags |= SAS_TASK_STATE_ABORTED; + complete(&task->slow_task->completion); + } spin_unlock_irqrestore(&task->task_state_lock, flags); - - complete(&task->slow_task->completion); } static void smp_task_done(struct sas_task *task) { - if (!del_timer(&task->slow_task->timer)) - return; + del_timer(&task->slow_task->timer); complete(&task->slow_task->completion); } diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/qla_attr.c index ac12ee844bfc..31c29a5d1f38 100644 --- a/drivers/scsi/qla2xxx/qla_attr.c +++ b/drivers/scsi/qla2xxx/qla_attr.c @@ -431,7 +431,7 @@ qla2x00_sysfs_write_optrom_ctl(struct file *filp, struct kobject *kobj, } ha->optrom_region_start = start; - ha->optrom_region_size = start + size; + ha->optrom_region_size = size; ha->optrom_state = QLA_SREADING; ha->optrom_buffer = vmalloc(ha->optrom_region_size); @@ -504,7 +504,7 @@ qla2x00_sysfs_write_optrom_ctl(struct file *filp, struct kobject *kobj, } ha->optrom_region_start = start; - ha->optrom_region_size = start + size; + ha->optrom_region_size = size; ha->optrom_state = QLA_SWRITING; ha->optrom_buffer = vmalloc(ha->optrom_region_size); diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c index f9f899ec9427..c158967b59d7 100644 --- a/drivers/scsi/qla4xxx/ql4_os.c +++ b/drivers/scsi/qla4xxx/ql4_os.c @@ -3207,6 +3207,8 @@ static int qla4xxx_conn_bind(struct iscsi_cls_session *cls_session, if (iscsi_conn_bind(cls_session, cls_conn, is_leading)) return -EINVAL; ep = iscsi_lookup_endpoint(transport_fd); + if (!ep) + return -EINVAL; conn = cls_conn->dd_data; qla_conn = conn->dd_data; qla_conn->qla_ep = ep->dd_data; diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c index 44b7a69d022a..45cd4cf93af3 100644 --- a/drivers/scsi/storvsc_drv.c +++ b/drivers/scsi/storvsc_drv.c @@ -613,13 +613,22 @@ static void handle_sc_creation(struct vmbus_channel *new_sc) static void handle_multichannel_storage(struct hv_device *device, int max_chns) { struct storvsc_device *stor_device; - int num_cpus = num_online_cpus(); int num_sc; struct storvsc_cmd_request *request; struct vstor_packet *vstor_packet; int ret, t; - num_sc = ((max_chns > num_cpus) ? num_cpus : max_chns); + /* + * If the number of CPUs is artificially restricted, such as + * with maxcpus=1 on the kernel boot line, Hyper-V could offer + * sub-channels >= the number of CPUs. These sub-channels + * should not be created. The primary channel is already created + * and assigned to one CPU, so check against # CPUs - 1. + */ + num_sc = min((int)(num_online_cpus() - 1), max_chns); + if (!num_sc) + return; + stor_device = get_out_stor_device(device); if (!stor_device) return; 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/staging/iio/addac/adt7316.c b/drivers/staging/iio/addac/adt7316.c index 3adc4516918c..8c5cfb9400d0 100644 --- a/drivers/staging/iio/addac/adt7316.c +++ b/drivers/staging/iio/addac/adt7316.c @@ -47,6 +47,8 @@ #define ADT7516_MSB_AIN3 0xA #define ADT7516_MSB_AIN4 0xB #define ADT7316_DA_DATA_BASE 0x10 +#define ADT7316_DA_10_BIT_LSB_SHIFT 6 +#define ADT7316_DA_12_BIT_LSB_SHIFT 4 #define ADT7316_DA_MSB_DATA_REGS 4 #define ADT7316_LSB_DAC_A 0x10 #define ADT7316_MSB_DAC_A 0x11 @@ -1092,7 +1094,7 @@ static ssize_t adt7316_store_DAC_internal_Vref(struct device *dev, ldac_config = chip->ldac_config & (~ADT7516_DAC_IN_VREF_MASK); if (data & 0x1) ldac_config |= ADT7516_DAC_AB_IN_VREF; - else if (data & 0x2) + if (data & 0x2) ldac_config |= ADT7516_DAC_CD_IN_VREF; } else { ret = kstrtou8(buf, 16, &data); @@ -1414,7 +1416,7 @@ static IIO_DEVICE_ATTR(ex_analog_temp_offset, S_IRUGO | S_IWUSR, static ssize_t adt7316_show_DAC(struct adt7316_chip_info *chip, int channel, char *buf) { - u16 data; + u16 data = 0; u8 msb, lsb, offset; int ret; @@ -1439,7 +1441,11 @@ static ssize_t adt7316_show_DAC(struct adt7316_chip_info *chip, if (ret) return -EIO; - data = (msb << offset) + (lsb & ((1 << offset) - 1)); + if (chip->dac_bits == 12) + data = lsb >> ADT7316_DA_12_BIT_LSB_SHIFT; + else if (chip->dac_bits == 10) + data = lsb >> ADT7316_DA_10_BIT_LSB_SHIFT; + data |= msb << offset; return sprintf(buf, "%d\n", data); } @@ -1447,7 +1453,7 @@ static ssize_t adt7316_show_DAC(struct adt7316_chip_info *chip, static ssize_t adt7316_store_DAC(struct adt7316_chip_info *chip, int channel, const char *buf, size_t len) { - u8 msb, lsb, offset; + u8 msb, lsb, lsb_reg, offset; u16 data; int ret; @@ -1465,9 +1471,13 @@ static ssize_t adt7316_store_DAC(struct adt7316_chip_info *chip, return -EINVAL; if (chip->dac_bits > 8) { - lsb = data & (1 << offset); + lsb = data & ((1 << offset) - 1); + if (chip->dac_bits == 12) + lsb_reg = lsb << ADT7316_DA_12_BIT_LSB_SHIFT; + else + lsb_reg = lsb << ADT7316_DA_10_BIT_LSB_SHIFT; ret = chip->bus.write(chip->bus.client, - ADT7316_DA_DATA_BASE + channel * 2, lsb); + ADT7316_DA_DATA_BASE + channel * 2, lsb_reg); if (ret) return -EIO; } diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 17a22073d226..032f3c13b8c4 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1448,7 +1448,7 @@ static int __init sc16is7xx_init(void) ret = i2c_add_driver(&sc16is7xx_i2c_uart_driver); if (ret < 0) { pr_err("failed to init sc16is7xx i2c --> %d\n", ret); - return ret; + goto err_i2c; } #endif @@ -1456,10 +1456,18 @@ static int __init sc16is7xx_init(void) ret = spi_register_driver(&sc16is7xx_spi_uart_driver); if (ret < 0) { pr_err("failed to init sc16is7xx spi --> %d\n", ret); - return ret; + goto err_spi; } #endif return ret; + +err_spi: +#ifdef CONFIG_SERIAL_SC16IS7XX_I2C + i2c_del_driver(&sc16is7xx_i2c_uart_driver); +#endif +err_i2c: + uart_unregister_driver(&sc16is7xx_uart); + return ret; } module_init(sc16is7xx_init); diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 7dae981b66b1..8089e5820be4 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -470,11 +470,6 @@ static int usb_unbind_interface(struct device *dev) pm_runtime_disable(dev); pm_runtime_set_suspended(dev); - /* Undo any residual pm_autopm_get_interface_* calls */ - for (r = atomic_read(&intf->pm_usage_cnt); r > 0; --r) - usb_autopm_put_interface_no_suspend(intf); - atomic_set(&intf->pm_usage_cnt, 0); - if (!error) usb_autosuspend_device(udev); @@ -1638,7 +1633,6 @@ void usb_autopm_put_interface(struct usb_interface *intf) int status; usb_mark_last_busy(udev); - atomic_dec(&intf->pm_usage_cnt); status = pm_runtime_put_sync(&intf->dev); dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), @@ -1667,7 +1661,6 @@ void usb_autopm_put_interface_async(struct usb_interface *intf) int status; usb_mark_last_busy(udev); - atomic_dec(&intf->pm_usage_cnt); status = pm_runtime_put(&intf->dev); dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), @@ -1689,7 +1682,6 @@ void usb_autopm_put_interface_no_suspend(struct usb_interface *intf) struct usb_device *udev = interface_to_usbdev(intf); usb_mark_last_busy(udev); - atomic_dec(&intf->pm_usage_cnt); pm_runtime_put_noidle(&intf->dev); } EXPORT_SYMBOL_GPL(usb_autopm_put_interface_no_suspend); @@ -1720,8 +1712,6 @@ int usb_autopm_get_interface(struct usb_interface *intf) status = pm_runtime_get_sync(&intf->dev); if (status < 0) pm_runtime_put_sync(&intf->dev); - else - atomic_inc(&intf->pm_usage_cnt); dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), status); @@ -1755,8 +1745,6 @@ int usb_autopm_get_interface_async(struct usb_interface *intf) status = pm_runtime_get(&intf->dev); if (status < 0 && status != -EINPROGRESS) pm_runtime_put_noidle(&intf->dev); - else - atomic_inc(&intf->pm_usage_cnt); dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), status); @@ -1780,7 +1768,6 @@ void usb_autopm_get_interface_no_resume(struct usb_interface *intf) struct usb_device *udev = interface_to_usbdev(intf); usb_mark_last_busy(udev); - atomic_inc(&intf->pm_usage_cnt); pm_runtime_get_noresume(&intf->dev); } EXPORT_SYMBOL_GPL(usb_autopm_get_interface_no_resume); @@ -1901,14 +1888,11 @@ int usb_runtime_idle(struct device *dev) return -EBUSY; } -int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) +static int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) { struct usb_hcd *hcd = bus_to_hcd(udev->bus); int ret = -EPERM; - if (enable && !udev->usb2_hw_lpm_allowed) - return 0; - if (hcd->driver->set_usb2_hw_lpm) { ret = hcd->driver->set_usb2_hw_lpm(hcd, udev, enable); if (!ret) @@ -1918,6 +1902,24 @@ int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) return ret; } +int usb_enable_usb2_hardware_lpm(struct usb_device *udev) +{ + if (!udev->usb2_hw_lpm_capable || + !udev->usb2_hw_lpm_allowed || + udev->usb2_hw_lpm_enabled) + return 0; + + return usb_set_usb2_hardware_lpm(udev, 1); +} + +int usb_disable_usb2_hardware_lpm(struct usb_device *udev) +{ + if (!udev->usb2_hw_lpm_enabled) + return 0; + + return usb_set_usb2_hardware_lpm(udev, 0); +} + #endif /* CONFIG_PM */ struct bus_type usb_bus_type = { diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 4c1cfbf23487..9408bfef34f3 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3127,8 +3127,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) } /* disable USB2 hardware LPM */ - if (udev->usb2_hw_lpm_enabled == 1) - usb_set_usb2_hardware_lpm(udev, 0); + usb_disable_usb2_hardware_lpm(udev); if (usb_disable_ltm(udev)) { dev_err(&udev->dev, "Failed to disable LTM before suspend\n."); @@ -3174,8 +3173,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) usb_enable_ltm(udev); err_ltm: /* Try to enable USB2 hardware LPM again */ - if (udev->usb2_hw_lpm_capable == 1) - usb_set_usb2_hardware_lpm(udev, 1); + usb_enable_usb2_hardware_lpm(udev); if (udev->do_remote_wakeup) (void) usb_disable_remote_wakeup(udev); @@ -3456,8 +3454,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) hub_port_logical_disconnect(hub, port1); } else { /* Try to enable USB2 hardware LPM */ - if (udev->usb2_hw_lpm_capable == 1) - usb_set_usb2_hardware_lpm(udev, 1); + usb_enable_usb2_hardware_lpm(udev); /* Try to enable USB3 LTM and LPM */ usb_enable_ltm(udev); @@ -4283,7 +4280,7 @@ static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev) if ((udev->bos->ext_cap->bmAttributes & cpu_to_le32(USB_BESL_SUPPORT)) || connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { udev->usb2_hw_lpm_allowed = 1; - usb_set_usb2_hardware_lpm(udev, 1); + usb_enable_usb2_hardware_lpm(udev); } } @@ -5434,8 +5431,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) /* Disable USB2 hardware LPM. * It will be re-enabled by the enumeration process. */ - if (udev->usb2_hw_lpm_enabled == 1) - usb_set_usb2_hardware_lpm(udev, 0); + usb_disable_usb2_hardware_lpm(udev); /* Disable LPM and LTM while we reset the device and reinstall the alt * settings. Device-initiated LPM settings, and system exit latency @@ -5545,7 +5541,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) done: /* Now that the alt settings are re-installed, enable LTM and LPM. */ - usb_set_usb2_hardware_lpm(udev, 1); + usb_enable_usb2_hardware_lpm(udev); usb_unlocked_enable_lpm(udev); usb_enable_ltm(udev); usb_release_bos_descriptor(udev); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 530817c6783d..ec35df3813c6 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -821,9 +821,11 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) if (dev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; - if (size <= 0 || !buf || !index) + if (size <= 0 || !buf) return -EINVAL; buf[0] = 0; + if (index <= 0 || index >= 256) + return -EINVAL; tbuf = kmalloc(256, GFP_NOIO); if (!tbuf) return -ENOMEM; @@ -1185,8 +1187,7 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0) dev->actconfig->interface[i] = NULL; } - if (dev->usb2_hw_lpm_enabled == 1) - usb_set_usb2_hardware_lpm(dev, 0); + usb_disable_usb2_hardware_lpm(dev); usb_unlocked_disable_lpm(dev); usb_disable_ltm(dev); diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 65b6e6b84043..6dc0f4e25cf3 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -472,7 +472,10 @@ static ssize_t usb2_hardware_lpm_store(struct device *dev, if (!ret) { udev->usb2_hw_lpm_allowed = value; - ret = usb_set_usb2_hardware_lpm(udev, value); + if (value) + ret = usb_enable_usb2_hardware_lpm(udev); + else + ret = usb_disable_usb2_hardware_lpm(udev); } usb_unlock_device(udev); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index fbff25ff23a9..dde0e997799e 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -84,7 +84,8 @@ extern int usb_remote_wakeup(struct usb_device *dev); extern int usb_runtime_suspend(struct device *dev); extern int usb_runtime_resume(struct device *dev); extern int usb_runtime_idle(struct device *dev); -extern int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable); +extern int usb_enable_usb2_hardware_lpm(struct usb_device *udev); +extern int usb_disable_usb2_hardware_lpm(struct usb_device *udev); #else @@ -104,7 +105,12 @@ static inline int usb_autoresume_device(struct usb_device *udev) return 0; } -static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) +static inline int usb_enable_usb2_hardware_lpm(struct usb_device *udev) +{ + return 0; +} + +static inline int usb_disable_usb2_hardware_lpm(struct usb_device *udev) { return 0; } diff --git a/drivers/usb/gadget/function/f_gsi.h b/drivers/usb/gadget/function/f_gsi.h index 829e1fcbe156..96f1b5011960 100644 --- a/drivers/usb/gadget/function/f_gsi.h +++ b/drivers/usb/gadget/function/f_gsi.h @@ -31,7 +31,7 @@ #define GSI_MBIM_CTRL_NAME "android_mbim" #define GSI_DPL_CTRL_NAME "dpl_ctrl" #define GSI_CTRL_NAME_LEN (sizeof(GSI_MBIM_CTRL_NAME)+2) -#define GSI_MAX_CTRL_PKT_SIZE 4096 +#define GSI_MAX_CTRL_PKT_SIZE 8192 #define GSI_CTRL_DTR (1 << 0) diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index 3b6e34fc032b..553922c3be85 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -962,6 +962,7 @@ net2272_dequeue(struct usb_ep *_ep, struct usb_request *_req) break; } if (&req->req != _req) { + ep->stopped = stopped; spin_unlock_irqrestore(&ep->dev->lock, flags); return -EINVAL; } diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 8efeadf30b4d..3a8d056a5d16 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -870,9 +870,6 @@ static void start_queue(struct net2280_ep *ep, u32 dmactl, u32 td_dma) (void) readl(&ep->dev->pci->pcimstctl); writel(BIT(DMA_START), &dma->dmastat); - - if (!ep->is_in) - stop_out_naking(ep); } static void start_dma(struct net2280_ep *ep, struct net2280_request *req) @@ -911,6 +908,7 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) writel(BIT(DMA_START), &dma->dmastat); return; } + stop_out_naking(ep); } tmp = dmactl_default; @@ -1272,9 +1270,9 @@ static int net2280_dequeue(struct usb_ep *_ep, struct usb_request *_req) break; } if (&req->req != _req) { + ep->stopped = stopped; spin_unlock_irqrestore(&ep->dev->lock, flags); - dev_err(&ep->dev->pdev->dev, "%s: Request mismatch\n", - __func__); + ep_dbg(ep->dev, "%s: Request mismatch\n", __func__); return -EINVAL; } diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index d5434e7a3b2e..86f9944f337d 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -3214,6 +3214,9 @@ static int __init u132_hcd_init(void) printk(KERN_INFO "driver %s\n", hcd_name); workqueue = create_singlethread_workqueue("u132"); retval = platform_driver_register(&u132_platform_driver); + if (retval) + destroy_workqueue(workqueue); + return retval; } diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c index 5594a4a4a83f..a8b6d0036e5d 100644 --- a/drivers/usb/misc/yurex.c +++ b/drivers/usb/misc/yurex.c @@ -332,6 +332,7 @@ static void yurex_disconnect(struct usb_interface *interface) usb_deregister_dev(interface, &yurex_class); /* prevent more I/O from starting */ + usb_poison_urb(dev->urb); mutex_lock(&dev->io_mutex); dev->interface = NULL; mutex_unlock(&dev->io_mutex); diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 54e170dd3dad..faead4f32b1c 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -350,39 +350,59 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; unsigned char *data = urb->transfer_buffer; unsigned long flags; + bool stopped = false; + int status = urb->status; int i; for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { if (urb == port->read_urbs[i]) break; } - set_bit(i, &port->read_urbs_free); dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i, urb->actual_length); - switch (urb->status) { + switch (status) { case 0: + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, + data); + port->serial->type->process_read_urb(urb); break; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: dev_dbg(&port->dev, "%s - urb stopped: %d\n", - __func__, urb->status); - return; + __func__, status); + stopped = true; + break; case -EPIPE: dev_err(&port->dev, "%s - urb stopped: %d\n", - __func__, urb->status); - return; + __func__, status); + stopped = true; + break; default: dev_dbg(&port->dev, "%s - nonzero urb status: %d\n", - __func__, urb->status); - goto resubmit; + __func__, status); + break; } - usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data); - port->serial->type->process_read_urb(urb); + /* + * Make sure URB processing is done before marking as free to avoid + * racing with unthrottle() on another CPU. Matches the barriers + * implied by the test_and_clear_bit() in + * usb_serial_generic_submit_read_urb(). + */ + smp_mb__before_atomic(); + set_bit(i, &port->read_urbs_free); + /* + * Make sure URB is marked as free before checking the throttled flag + * to avoid racing with unthrottle() on another CPU. Matches the + * smp_mb() in unthrottle(). + */ + smp_mb__after_atomic(); + + if (stopped) + return; -resubmit: /* Throttle the device if requested by tty */ spin_lock_irqsave(&port->lock, flags); port->throttled = port->throttle_req; @@ -399,6 +419,7 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb) { unsigned long flags; struct usb_serial_port *port = urb->context; + int status = urb->status; int i; for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { @@ -410,22 +431,22 @@ void usb_serial_generic_write_bulk_callback(struct urb *urb) set_bit(i, &port->write_urbs_free); spin_unlock_irqrestore(&port->lock, flags); - switch (urb->status) { + switch (status) { case 0: break; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: dev_dbg(&port->dev, "%s - urb stopped: %d\n", - __func__, urb->status); + __func__, status); return; case -EPIPE: dev_err_console(port, "%s - urb stopped: %d\n", - __func__, urb->status); + __func__, status); return; default: dev_err_console(port, "%s - nonzero urb status: %d\n", - __func__, urb->status); + __func__, status); goto resubmit; } @@ -456,6 +477,12 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty) port->throttled = port->throttle_req = 0; spin_unlock_irq(&port->lock); + /* + * Matches the smp_mb__after_atomic() in + * usb_serial_generic_read_bulk_callback(). + */ + smp_mb(); + if (was_throttled) usb_serial_generic_submit_read_urbs(port, GFP_KERNEL); } diff --git a/drivers/usb/storage/realtek_cr.c b/drivers/usb/storage/realtek_cr.c index 20433563a601..be432bec0c5b 100644 --- a/drivers/usb/storage/realtek_cr.c +++ b/drivers/usb/storage/realtek_cr.c @@ -772,18 +772,16 @@ static void rts51x_suspend_timer_fn(unsigned long data) break; case RTS51X_STAT_IDLE: case RTS51X_STAT_SS: - usb_stor_dbg(us, "RTS51X_STAT_SS, intf->pm_usage_cnt:%d, power.usage:%d\n", - atomic_read(&us->pusb_intf->pm_usage_cnt), + usb_stor_dbg(us, "RTS51X_STAT_SS, power.usage:%d\n", atomic_read(&us->pusb_intf->dev.power.usage_count)); - if (atomic_read(&us->pusb_intf->pm_usage_cnt) > 0) { + if (atomic_read(&us->pusb_intf->dev.power.usage_count) > 0) { usb_stor_dbg(us, "Ready to enter SS state\n"); rts51x_set_stat(chip, RTS51X_STAT_SS); /* ignore mass storage interface's children */ pm_suspend_ignore_children(&us->pusb_intf->dev, true); usb_autopm_put_interface_async(us->pusb_intf); - usb_stor_dbg(us, "RTS51X_STAT_SS 01, intf->pm_usage_cnt:%d, power.usage:%d\n", - atomic_read(&us->pusb_intf->pm_usage_cnt), + usb_stor_dbg(us, "RTS51X_STAT_SS 01, power.usage:%d\n", atomic_read(&us->pusb_intf->dev.power.usage_count)); } break; @@ -816,11 +814,10 @@ static void rts51x_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) int ret; if (working_scsi(srb)) { - usb_stor_dbg(us, "working scsi, intf->pm_usage_cnt:%d, power.usage:%d\n", - atomic_read(&us->pusb_intf->pm_usage_cnt), + usb_stor_dbg(us, "working scsi, power.usage:%d\n", atomic_read(&us->pusb_intf->dev.power.usage_count)); - if (atomic_read(&us->pusb_intf->pm_usage_cnt) <= 0) { + if (atomic_read(&us->pusb_intf->dev.power.usage_count) <= 0) { ret = usb_autopm_get_interface(us->pusb_intf); usb_stor_dbg(us, "working scsi, ret=%d\n", ret); } diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 6cac8f26b97a..e657b111b320 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -772,23 +772,33 @@ static int uas_slave_alloc(struct scsi_device *sdev) { struct uas_dev_info *devinfo = (struct uas_dev_info *)sdev->host->hostdata; + int maxp; sdev->hostdata = devinfo; - /* USB has unusual DMA-alignment requirements: Although the - * starting address of each scatter-gather element doesn't matter, - * the length of each element except the last must be divisible - * by the Bulk maxpacket value. There's currently no way to - * express this by block-layer constraints, so we'll cop out - * and simply require addresses to be aligned at 512-byte - * boundaries. This is okay since most block I/O involves - * hardware sectors that are multiples of 512 bytes in length, - * and since host controllers up through USB 2.0 have maxpacket - * values no larger than 512. - * - * But it doesn't suffice for Wireless USB, where Bulk maxpacket - * values can be as large as 2048. To make that work properly - * will require changes to the block layer. + /* + * We have two requirements here. We must satisfy the requirements + * of the physical HC and the demands of the protocol, as we + * definitely want no additional memory allocation in this path + * ruling out using bounce buffers. + * + * For a transmission on USB to continue we must never send + * a package that is smaller than maxpacket. Hence the length of each + * scatterlist element except the last must be divisible by the + * Bulk maxpacket value. + * If the HC does not ensure that through SG, + * the upper layer must do that. We must assume nothing + * about the capabilities off the HC, so we use the most + * pessimistic requirement. + */ + + maxp = usb_maxpacket(devinfo->udev, devinfo->data_in_pipe, 0); + blk_queue_virt_boundary(sdev->request_queue, maxp - 1); + + /* + * The protocol has no requirements on alignment in the strict sense. + * Controllers may or may not have alignment restrictions. + * As this is not exported, we use an extremely conservative guess. */ blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); diff --git a/drivers/usb/usbip/stub_rx.c b/drivers/usb/usbip/stub_rx.c index 56cacb68040c..808e3a317954 100644 --- a/drivers/usb/usbip/stub_rx.c +++ b/drivers/usb/usbip/stub_rx.c @@ -380,22 +380,10 @@ static int get_pipe(struct stub_device *sdev, struct usbip_header *pdu) } if (usb_endpoint_xfer_isoc(epd)) { - /* validate packet size and number of packets */ - unsigned int maxp, packets, bytes; - -#define USB_EP_MAXP_MULT_SHIFT 11 -#define USB_EP_MAXP_MULT_MASK (3 << USB_EP_MAXP_MULT_SHIFT) -#define USB_EP_MAXP_MULT(m) \ - (((m) & USB_EP_MAXP_MULT_MASK) >> USB_EP_MAXP_MULT_SHIFT) - - maxp = usb_endpoint_maxp(epd); - maxp *= (USB_EP_MAXP_MULT( - __le16_to_cpu(epd->wMaxPacketSize)) + 1); - bytes = pdu->u.cmd_submit.transfer_buffer_length; - packets = DIV_ROUND_UP(bytes, maxp); - + /* validate number of packets */ if (pdu->u.cmd_submit.number_of_packets < 0 || - pdu->u.cmd_submit.number_of_packets > packets) { + pdu->u.cmd_submit.number_of_packets > + USBIP_MAX_ISO_PACKETS) { dev_err(&sdev->udev->dev, "CMD_SUBMIT: isoc invalid num packets %d\n", pdu->u.cmd_submit.number_of_packets); diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h index 0fc5ace57c0e..af903aa4ad90 100644 --- a/drivers/usb/usbip/usbip_common.h +++ b/drivers/usb/usbip/usbip_common.h @@ -134,6 +134,13 @@ extern struct device_attribute dev_attr_usbip_debug; #define USBIP_DIR_OUT 0x00 #define USBIP_DIR_IN 0x01 +/* + * Arbitrary limit for the maximum number of isochronous packets in an URB, + * compare for example the uhci_submit_isochronous function in + * drivers/usb/host/uhci-q.c + */ +#define USBIP_MAX_ISO_PACKETS 1024 + /** * struct usbip_header_basic - data pertinent to every request * @command: the usbip request type diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index b31b84f56e8f..47b229fa5e8e 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -1191,11 +1191,11 @@ static void __init vfio_pci_fill_ids(void) rc = pci_add_dynid(&vfio_pci_driver, vendor, device, subvendor, subdevice, class, class_mask, 0); if (rc) - pr_warn("failed to add dynamic id [%04hx:%04hx[%04hx:%04hx]] class %#08x/%08x (%d)\n", + pr_warn("failed to add dynamic id [%04x:%04x[%04x:%04x]] class %#08x/%08x (%d)\n", vendor, device, subvendor, subdevice, class, class_mask, rc); else - pr_info("add [%04hx:%04hx[%04hx:%04hx]] class %#08x/%08x\n", + pr_info("add [%04x:%04x[%04x:%04x]] class %#08x/%08x\n", vendor, device, subvendor, subdevice, class, class_mask); } diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 2fa280671c1e..875634d0d020 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -53,10 +53,16 @@ module_param_named(disable_hugepages, MODULE_PARM_DESC(disable_hugepages, "Disable VFIO IOMMU support for IOMMU hugepages."); +static unsigned int dma_entry_limit __read_mostly = U16_MAX; +module_param_named(dma_entry_limit, dma_entry_limit, uint, 0644); +MODULE_PARM_DESC(dma_entry_limit, + "Maximum number of user DMA mappings per container (65535)."); + struct vfio_iommu { struct list_head domain_list; struct mutex lock; struct rb_root dma_list; + unsigned int dma_avail; bool v2; bool nesting; }; @@ -382,6 +388,7 @@ static void vfio_remove_dma(struct vfio_iommu *iommu, struct vfio_dma *dma) vfio_unmap_unpin(iommu, dma); vfio_unlink_dma(iommu, dma); kfree(dma); + iommu->dma_avail++; } static unsigned long vfio_pgsize_bitmap(struct vfio_iommu *iommu) @@ -582,12 +589,18 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu, return -EEXIST; } + if (!iommu->dma_avail) { + mutex_unlock(&iommu->lock); + return -ENOSPC; + } + dma = kzalloc(sizeof(*dma), GFP_KERNEL); if (!dma) { mutex_unlock(&iommu->lock); return -ENOMEM; } + iommu->dma_avail--; dma->iova = iova; dma->vaddr = vaddr; dma->prot = prot; @@ -903,6 +916,7 @@ static void *vfio_iommu_type1_open(unsigned long arg) INIT_LIST_HEAD(&iommu->domain_list); iommu->dma_list = RB_ROOT; + iommu->dma_avail = dma_entry_limit; mutex_init(&iommu->lock); return iommu; diff --git a/drivers/virt/fsl_hypervisor.c b/drivers/virt/fsl_hypervisor.c index 590a0f51a249..9f96c7e61387 100644 --- a/drivers/virt/fsl_hypervisor.c +++ b/drivers/virt/fsl_hypervisor.c @@ -215,6 +215,9 @@ static long ioctl_memcpy(struct fsl_hv_ioctl_memcpy __user *p) * hypervisor. */ lb_offset = param.local_vaddr & (PAGE_SIZE - 1); + if (param.count == 0 || + param.count > U64_MAX - lb_offset - PAGE_SIZE + 1) + return -EINVAL; num_pages = (param.count + lb_offset + PAGE_SIZE - 1) >> PAGE_SHIFT; /* Allocate the buffers we need */ @@ -335,8 +338,8 @@ static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set) struct fsl_hv_ioctl_prop param; char __user *upath, *upropname; void __user *upropval; - char *path = NULL, *propname = NULL; - void *propval = NULL; + char *path, *propname; + void *propval; int ret = 0; /* Get the parameters from the user. */ @@ -348,32 +351,30 @@ static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set) upropval = (void __user *)(uintptr_t)param.propval; path = strndup_user(upath, FH_DTPROP_MAX_PATHLEN); - if (IS_ERR(path)) { - ret = PTR_ERR(path); - goto out; - } + if (IS_ERR(path)) + return PTR_ERR(path); propname = strndup_user(upropname, FH_DTPROP_MAX_PATHLEN); if (IS_ERR(propname)) { ret = PTR_ERR(propname); - goto out; + goto err_free_path; } if (param.proplen > FH_DTPROP_MAX_PROPLEN) { ret = -EINVAL; - goto out; + goto err_free_propname; } propval = kmalloc(param.proplen, GFP_KERNEL); if (!propval) { ret = -ENOMEM; - goto out; + goto err_free_propname; } if (set) { if (copy_from_user(propval, upropval, param.proplen)) { ret = -EFAULT; - goto out; + goto err_free_propval; } param.ret = fh_partition_set_dtprop(param.handle, @@ -392,7 +393,7 @@ static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set) if (copy_to_user(upropval, propval, param.proplen) || put_user(param.proplen, &p->proplen)) { ret = -EFAULT; - goto out; + goto err_free_propval; } } } @@ -400,10 +401,12 @@ static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set) if (put_user(param.ret, &p->ret)) ret = -EFAULT; -out: - kfree(path); +err_free_propval: kfree(propval); +err_free_propname: kfree(propname); +err_free_path: + kfree(path); return ret; } diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c index 59d74d1b47a8..2287e1be0e55 100644 --- a/drivers/w1/masters/ds2490.c +++ b/drivers/w1/masters/ds2490.c @@ -1039,15 +1039,15 @@ static int ds_probe(struct usb_interface *intf, /* alternative 3, 1ms interrupt (greatly speeds search), 64 byte bulk */ alt = 3; err = usb_set_interface(dev->udev, - intf->altsetting[alt].desc.bInterfaceNumber, alt); + intf->cur_altsetting->desc.bInterfaceNumber, alt); if (err) { dev_err(&dev->udev->dev, "Failed to set alternative setting %d " "for %d interface: err=%d.\n", alt, - intf->altsetting[alt].desc.bInterfaceNumber, err); + intf->cur_altsetting->desc.bInterfaceNumber, err); goto err_out_clear; } - iface_desc = &intf->altsetting[alt]; + iface_desc = intf->cur_altsetting; if (iface_desc->desc.bNumEndpoints != NUM_EP-1) { pr_info("Num endpoints=%d. It is not DS9490R.\n", iface_desc->desc.bNumEndpoints); |
