summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/ata/libata-zpodd.c34
-rw-r--r--drivers/base/cpu.c8
-rw-r--r--drivers/block/loop.c42
-rw-r--r--drivers/block/loop.h1
-rw-r--r--drivers/block/xsysace.c2
-rw-r--r--drivers/gpu/ipu-v3/ipu-dp.c12
-rw-r--r--drivers/hid/hid-debug.c5
-rw-r--r--drivers/hid/hid-input.c6
-rw-r--r--drivers/hwtracing/intel_th/gth.c2
-rw-r--r--drivers/iio/adc/xilinx-xadc-core.c2
-rw-r--r--drivers/input/keyboard/snvs_pwrkey.c6
-rw-r--r--drivers/iommu/amd_iommu_init.c2
-rw-r--r--drivers/md/raid5.c19
-rw-r--r--drivers/media/i2c/ov7670.c16
-rw-r--r--drivers/media/radio/Kconfig7
-rw-r--r--drivers/media/radio/Makefile2
-rw-r--r--drivers/media/radio/silabs/Makefile1
-rw-r--r--drivers/media/radio/silabs/radio-silabs.c3963
-rw-r--r--drivers/media/radio/silabs/radio-silabs.h532
-rw-r--r--drivers/media/v4l2-core/v4l2-dev.c3
-rw-r--r--drivers/media/v4l2-core/v4l2-ioctl.c4
-rw-r--r--drivers/net/bonding/bond_options.c7
-rw-r--r--drivers/net/bonding/bond_sysfs_slave.c4
-rw-r--r--drivers/net/ethernet/broadcom/bnxt/bnxt.c9
-rw-r--r--drivers/net/ethernet/freescale/ucc_geth_ethtool.c8
-rw-r--r--drivers/net/ethernet/hisilicon/hns/hnae.c4
-rw-r--r--drivers/net/ethernet/hisilicon/hns/hns_enet.c7
-rw-r--r--drivers/net/ethernet/ibm/ehea/ehea_main.c1
-rw-r--r--drivers/net/ethernet/intel/igb/e1000_defines.h2
-rw-r--r--drivers/net/ethernet/intel/igb/igb_main.c57
-rw-r--r--drivers/net/ethernet/micrel/ks8851.c36
-rw-r--r--drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c2
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_main.c4
-rw-r--r--drivers/net/ethernet/ti/netcp_ethss.c8
-rw-r--r--drivers/net/ethernet/xilinx/xilinx_axienet_main.c2
-rw-r--r--drivers/net/slip/slhc.c2
-rw-r--r--drivers/net/team/team.c6
-rw-r--r--drivers/net/usb/ipheth.c33
-rw-r--r--drivers/net/wireless/cw1200/scan.c5
-rw-r--r--drivers/nvdimm/btt_devs.c18
-rw-r--r--drivers/platform/msm/ipa/ipa_v2/ipa_rt.c10
-rw-r--r--drivers/platform/x86/sony-laptop.c8
-rw-r--r--drivers/rtc/rtc-da9063.c7
-rw-r--r--drivers/rtc/rtc-sh.c2
-rw-r--r--drivers/s390/block/dasd_eckd.c6
-rw-r--r--drivers/s390/char/con3270.c2
-rw-r--r--drivers/s390/char/fs3270.c3
-rw-r--r--drivers/s390/char/raw3270.c3
-rw-r--r--drivers/s390/char/raw3270.h4
-rw-r--r--drivers/s390/char/tty3270.c3
-rw-r--r--drivers/s390/net/ctcm_main.c1
-rw-r--r--drivers/s390/scsi/zfcp_fc.c21
-rw-r--r--drivers/scsi/csiostor/csio_scsi.c5
-rw-r--r--drivers/scsi/libsas/sas_expander.c9
-rw-r--r--drivers/scsi/qla2xxx/qla_attr.c4
-rw-r--r--drivers/scsi/qla4xxx/ql4_os.c2
-rw-r--r--drivers/scsi/storvsc_drv.c13
-rw-r--r--drivers/soc/qcom/smcinvoke.c6
-rw-r--r--drivers/staging/iio/addac/adt7316.c22
-rw-r--r--drivers/tty/serial/sc16is7xx.c12
-rw-r--r--drivers/usb/core/driver.c36
-rw-r--r--drivers/usb/core/hub.c16
-rw-r--r--drivers/usb/core/message.c7
-rw-r--r--drivers/usb/core/sysfs.c5
-rw-r--r--drivers/usb/core/usb.h10
-rw-r--r--drivers/usb/gadget/function/f_gsi.h2
-rw-r--r--drivers/usb/gadget/udc/net2272.c1
-rw-r--r--drivers/usb/gadget/udc/net2280.c8
-rw-r--r--drivers/usb/host/u132-hcd.c3
-rw-r--r--drivers/usb/misc/yurex.c1
-rw-r--r--drivers/usb/serial/generic.c57
-rw-r--r--drivers/usb/storage/realtek_cr.c13
-rw-r--r--drivers/usb/storage/uas.c38
-rw-r--r--drivers/usb/usbip/stub_rx.c18
-rw-r--r--drivers/usb/usbip/usbip_common.h7
-rw-r--r--drivers/vfio/pci/vfio_pci.c4
-rw-r--r--drivers/vfio/vfio_iommu_type1.c14
-rw-r--r--drivers/virt/fsl_hypervisor.c29
-rw-r--r--drivers/w1/masters/ds2490.c6
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(&gth->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);