From 8b21eb17e749c9693eaea065f0eb95365006495c Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 25 Feb 2013 07:58:41 -0300 Subject: [media] radio-isa: fix querycap capabilities code The device_caps and capabilities fields were swapped. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-isa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c index 84b7b9f..fe0a4f8 100644 --- a/drivers/media/radio/radio-isa.c +++ b/drivers/media/radio/radio-isa.c @@ -51,8 +51,8 @@ static int radio_isa_querycap(struct file *file, void *priv, strlcpy(v->card, isa->drv->card, sizeof(v->card)); snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - v->device_caps = v->capabilities | V4L2_CAP_DEVICE_CAPS; + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } -- cgit v1.1 From 4ca286610f664acf3153634f3930acd2de993a9f Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 25 Feb 2013 08:00:15 -0300 Subject: [media] radio-rtrack2: fix mute bug Setting the frequency would unmute the card. Fixed the mute handling in the s_frequency code. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-rtrack2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c index b1f844c..09cfbc3 100644 --- a/drivers/media/radio/radio-rtrack2.c +++ b/drivers/media/radio/radio-rtrack2.c @@ -8,6 +8,8 @@ * * Converted to the radio-isa framework by Hans Verkuil * Converted to V4L2 API by Mauro Carvalho Chehab + * + * Fully tested with actual hardware and the v4l2-compliance tool. */ #include /* Modules */ @@ -81,8 +83,7 @@ static int rtrack2_s_frequency(struct radio_isa_card *isa, u32 freq) zero(isa); outb_p(0xc8, isa->io); - if (!v4l2_ctrl_g_ctrl(isa->mute)) - outb_p(0, isa->io); + outb_p(v4l2_ctrl_g_ctrl(isa->mute), isa->io); return 0; } -- cgit v1.1 From bf30690029a3b8572a6fc2facb77fbde86992988 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 26 Feb 2013 03:17:27 -0300 Subject: [media] Media: remove incorrect __init/__exit markups Even if bus is not hot-pluggable, the devices can be unbound from the driver via sysfs, so we should not be using __exit annotations on remove() methods. The only exception is drivers registered with platform_driver_probe() which specifically disables sysfs bind/unbind attributes. Similarly probe() methods should not be marked __init unless platform_driver_probe() is used. Signed-off-by: Dmitry Torokhov Acked-by: Guennadi Liakhovetski Acked-by: Sakari Ailus Acked-by: Timo Kokkonen Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 1507c9d..8ae8442d 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -328,7 +328,7 @@ exit: } /* radio_si4713_pdriver_remove - remove the device */ -static int __exit radio_si4713_pdriver_remove(struct platform_device *pdev) +static int radio_si4713_pdriver_remove(struct platform_device *pdev) { struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); struct radio_si4713_device *rsdev = container_of(v4l2_dev, @@ -350,7 +350,7 @@ static struct platform_driver radio_si4713_pdriver = { .name = "radio-si4713", }, .probe = radio_si4713_pdriver_probe, - .remove = __exit_p(radio_si4713_pdriver_remove), + .remove = radio_si4713_pdriver_remove, }; module_platform_driver(radio_si4713_pdriver); -- cgit v1.1 From 385624a8e74374f19a3db4f066dd21f213447a7a Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Tue, 19 Mar 2013 11:41:31 -0300 Subject: [media] media: radio: CodingStyle changes on si4713 Minor changes to make alignment match on open parenthesis. Signed-off-by: Eduardo Valentin Acked-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 53 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 27 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 8ae8442d..1e0d6fa 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -94,7 +94,7 @@ static int radio_si4713_querycap(struct file *file, void *priv, { strlcpy(capability->driver, "radio-si4713", sizeof(capability->driver)); strlcpy(capability->card, "Silicon Labs Si4713 Modulator", - sizeof(capability->card)); + sizeof(capability->card)); capability->capabilities = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; return 0; @@ -102,7 +102,7 @@ static int radio_si4713_querycap(struct file *file, void *priv, /* radio_si4713_queryctrl - enumerate control items */ static int radio_si4713_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) + struct v4l2_queryctrl *qc) { /* Must be sorted from low to high control ID! */ static const u32 user_ctrls[] = { @@ -152,7 +152,7 @@ static int radio_si4713_queryctrl(struct file *file, void *priv, return v4l2_ctrl_query_fill(qc, 0, 0, 0, 0); return v4l2_device_call_until_err(&rsdev->v4l2_dev, 0, core, - queryctrl, qc); + queryctrl, qc); } /* @@ -165,66 +165,66 @@ static inline struct v4l2_device *get_v4l2_dev(struct file *file) } static int radio_si4713_g_ext_ctrls(struct file *file, void *p, - struct v4l2_ext_controls *vecs) + struct v4l2_ext_controls *vecs) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - g_ext_ctrls, vecs); + g_ext_ctrls, vecs); } static int radio_si4713_s_ext_ctrls(struct file *file, void *p, - struct v4l2_ext_controls *vecs) + struct v4l2_ext_controls *vecs) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - s_ext_ctrls, vecs); + s_ext_ctrls, vecs); } static int radio_si4713_g_ctrl(struct file *file, void *p, - struct v4l2_control *vc) + struct v4l2_control *vc) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - g_ctrl, vc); + g_ctrl, vc); } static int radio_si4713_s_ctrl(struct file *file, void *p, - struct v4l2_control *vc) + struct v4l2_control *vc) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - s_ctrl, vc); + s_ctrl, vc); } static int radio_si4713_g_modulator(struct file *file, void *p, - struct v4l2_modulator *vm) + struct v4l2_modulator *vm) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - g_modulator, vm); + g_modulator, vm); } static int radio_si4713_s_modulator(struct file *file, void *p, - const struct v4l2_modulator *vm) + const struct v4l2_modulator *vm) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - s_modulator, vm); + s_modulator, vm); } static int radio_si4713_g_frequency(struct file *file, void *p, - struct v4l2_frequency *vf) + struct v4l2_frequency *vf) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - g_frequency, vf); + g_frequency, vf); } static int radio_si4713_s_frequency(struct file *file, void *p, - struct v4l2_frequency *vf) + struct v4l2_frequency *vf) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - s_frequency, vf); + s_frequency, vf); } static long radio_si4713_default(struct file *file, void *p, - bool valid_prio, int cmd, void *arg) + bool valid_prio, int cmd, void *arg) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - ioctl, cmd, arg); + ioctl, cmd, arg); } static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { @@ -285,13 +285,13 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) adapter = i2c_get_adapter(pdata->i2c_bus); if (!adapter) { dev_err(&pdev->dev, "Cannot get i2c adapter %d\n", - pdata->i2c_bus); + pdata->i2c_bus); rval = -ENODEV; goto unregister_v4l2_dev; } sd = v4l2_i2c_new_subdev_board(&rsdev->v4l2_dev, adapter, - pdata->subdev_board_info, NULL); + pdata->subdev_board_info, NULL); if (!sd) { dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n"); rval = -ENODEV; @@ -306,7 +306,7 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) } memcpy(rsdev->radio_dev, &radio_si4713_vdev_template, - sizeof(radio_si4713_vdev_template)); + sizeof(radio_si4713_vdev_template)); video_set_drvdata(rsdev->radio_dev, rsdev); if (video_register_device(rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) { dev_err(&pdev->dev, "Could not register video device.\n"); @@ -331,13 +331,12 @@ exit: static int radio_si4713_pdriver_remove(struct platform_device *pdev) { struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); - struct radio_si4713_device *rsdev = container_of(v4l2_dev, - struct radio_si4713_device, - v4l2_dev); struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next, struct v4l2_subdev, list); struct i2c_client *client = v4l2_get_subdevdata(sd); + struct radio_si4713_device *rsdev; + rsdev = container_of(v4l2_dev, struct radio_si4713_device, v4l2_dev); video_unregister_device(rsdev->radio_dev); i2c_put_adapter(client->adapter); v4l2_device_unregister(&rsdev->v4l2_dev); -- cgit v1.1 From a782ae09b709c21d872f23da3c12063d1f7fe61d Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Tue, 19 Mar 2013 11:41:32 -0300 Subject: [media] media: radio: correct module license (==> GPL v2) As per code header comment, changing the driver license entry to match the correct version. Signed-off-by: Eduardo Valentin Acked-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 1e0d6fa..e025c39 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -39,7 +39,7 @@ module_param(radio_nr, int, 0); MODULE_PARM_DESC(radio_nr, "Minor number for radio device (-1 ==> auto assign)"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Eduardo Valentin "); MODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter"); MODULE_VERSION("0.0.1"); -- cgit v1.1 From 04edd7b0382ccf059c316d28522a1215ca230420 Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Tue, 19 Mar 2013 11:41:33 -0300 Subject: [media] media: radio: add driver owner entry for radio-si4713 Simple addition of platform_driver->driver->owner for radio-si4713. Signed-off-by: Eduardo Valentin Acked-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index e025c39..7b66013 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -347,6 +347,7 @@ static int radio_si4713_pdriver_remove(struct platform_device *pdev) static struct platform_driver radio_si4713_pdriver = { .driver = { .name = "radio-si4713", + .owner = THIS_MODULE, }, .probe = radio_si4713_pdriver_probe, .remove = radio_si4713_pdriver_remove, -- cgit v1.1 From 04e1ac04e54e420908c9ac3e09025f8266dcdf57 Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Tue, 19 Mar 2013 11:41:34 -0300 Subject: [media] media: radio: add module alias entry for radio-si4713 Add MODULE_ALIAS entry for radio-si4713 platform driver. Signed-off-by: Eduardo Valentin Acked-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 7b66013..3df4339 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -43,6 +43,7 @@ MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Eduardo Valentin "); MODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter"); MODULE_VERSION("0.0.1"); +MODULE_ALIAS("platform:radio-si4713"); /* Driver state struct */ struct radio_si4713_device { -- cgit v1.1 From b530a447bb588fdf43fdf4eb909e4ee1921d47ac Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 19 Mar 2013 04:09:26 -0300 Subject: [media] v4l2: add const to argument of write-only s_frequency ioctl This ioctl is defined as IOW, so pass the argument as const. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/dsbr100.c | 2 +- drivers/media/radio/radio-cadet.c | 46 ++++++++++++------------ drivers/media/radio/radio-isa.c | 9 ++--- drivers/media/radio/radio-keene.c | 8 ++--- drivers/media/radio/radio-ma901.c | 2 +- drivers/media/radio/radio-miropcm20.c | 4 +-- drivers/media/radio/radio-mr800.c | 2 +- drivers/media/radio/radio-sf16fmi.c | 2 +- drivers/media/radio/radio-si4713.c | 2 +- drivers/media/radio/radio-tea5764.c | 2 +- drivers/media/radio/radio-tea5777.c | 2 +- drivers/media/radio/radio-timb.c | 2 +- drivers/media/radio/radio-wl1273.c | 2 +- drivers/media/radio/si470x/radio-si470x-common.c | 2 +- drivers/media/radio/si4713-i2c.c | 5 ++- drivers/media/radio/tef6862.c | 2 +- drivers/media/radio/wl128x/fmdrv_v4l2.c | 6 ++-- 17 files changed, 49 insertions(+), 51 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/dsbr100.c b/drivers/media/radio/dsbr100.c index 63b112b..e140a72 100644 --- a/drivers/media/radio/dsbr100.c +++ b/drivers/media/radio/dsbr100.c @@ -214,7 +214,7 @@ static int vidioc_s_tuner(struct file *file, void *priv, } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct dsbr100_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c index 643d80a..59be293 100644 --- a/drivers/media/radio/radio-cadet.c +++ b/drivers/media/radio/radio-cadet.c @@ -90,6 +90,26 @@ static u16 sigtable[2][4] = { { 2185, 4369, 13107, 65535 }, }; +static const struct v4l2_frequency_band bands[] = { + { + .index = 0, + .type = V4L2_TUNER_RADIO, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 8320, /* 520 kHz */ + .rangehigh = 26400, /* 1650 kHz */ + .modulation = V4L2_BAND_MODULATION_AM, + }, { + .index = 1, + .type = V4L2_TUNER_RADIO, + .capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | + V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 1400000, /* 87.5 MHz */ + .rangehigh = 1728000, /* 108.0 MHz */ + .modulation = V4L2_BAND_MODULATION_FM, + }, +}; + static int cadet_getstereo(struct cadet *dev) { @@ -196,6 +216,8 @@ static void cadet_setfreq(struct cadet *dev, unsigned freq) int i, j, test; int curvol; + freq = clamp(freq, bands[dev->is_fm_band].rangelow, + bands[dev->is_fm_band].rangehigh); dev->curfreq = freq; /* * Formulate a fifo command @@ -337,26 +359,6 @@ static int vidioc_querycap(struct file *file, void *priv, return 0; } -static const struct v4l2_frequency_band bands[] = { - { - .index = 0, - .type = V4L2_TUNER_RADIO, - .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, - .rangelow = 8320, /* 520 kHz */ - .rangehigh = 26400, /* 1650 kHz */ - .modulation = V4L2_BAND_MODULATION_AM, - }, { - .index = 1, - .type = V4L2_TUNER_RADIO, - .capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | - V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW | - V4L2_TUNER_CAP_FREQ_BANDS, - .rangelow = 1400000, /* 87.5 MHz */ - .rangehigh = 1728000, /* 108.0 MHz */ - .modulation = V4L2_BAND_MODULATION_FM, - }, -}; - static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { @@ -418,7 +420,7 @@ static int vidioc_g_frequency(struct file *file, void *priv, static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct cadet *dev = video_drvdata(file); @@ -426,8 +428,6 @@ static int vidioc_s_frequency(struct file *file, void *priv, return -EINVAL; dev->is_fm_band = f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2; - clamp(f->frequency, bands[dev->is_fm_band].rangelow, - bands[dev->is_fm_band].rangehigh); cadet_setfreq(dev, f->frequency); return 0; } diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c index fe0a4f8..0c1e27b 100644 --- a/drivers/media/radio/radio-isa.c +++ b/drivers/media/radio/radio-isa.c @@ -102,17 +102,18 @@ static int radio_isa_s_tuner(struct file *file, void *priv, } static int radio_isa_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct radio_isa_card *isa = video_drvdata(file); + u32 freq = f->frequency; int res; if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; - f->frequency = clamp(f->frequency, FREQ_LOW, FREQ_HIGH); - res = isa->drv->ops->s_frequency(isa, f->frequency); + freq = clamp(freq, FREQ_LOW, FREQ_HIGH); + res = isa->drv->ops->s_frequency(isa, freq); if (res == 0) - isa->freq = f->frequency; + isa->freq = freq; return res; } diff --git a/drivers/media/radio/radio-keene.c b/drivers/media/radio/radio-keene.c index 296941a..4c9ae76 100644 --- a/drivers/media/radio/radio-keene.c +++ b/drivers/media/radio/radio-keene.c @@ -215,15 +215,15 @@ static int vidioc_s_modulator(struct file *file, void *priv, } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct keene_device *radio = video_drvdata(file); + unsigned freq = f->frequency; if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; - f->frequency = clamp(f->frequency, - FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); - return keene_cmd_main(radio, f->frequency, true); + freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); + return keene_cmd_main(radio, freq, true); } static int vidioc_g_frequency(struct file *file, void *priv, diff --git a/drivers/media/radio/radio-ma901.c b/drivers/media/radio/radio-ma901.c index c61f590..7f85c6f 100644 --- a/drivers/media/radio/radio-ma901.c +++ b/drivers/media/radio/radio-ma901.c @@ -257,7 +257,7 @@ static int vidioc_s_tuner(struct file *file, void *priv, /* vidioc_s_frequency - set tuner radio frequency */ static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct ma901radio_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c index 3d0ff44..2b8d31d 100644 --- a/drivers/media/radio/radio-miropcm20.c +++ b/drivers/media/radio/radio-miropcm20.c @@ -131,14 +131,14 @@ static int vidioc_g_frequency(struct file *file, void *priv, static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct pcm20 *dev = video_drvdata(file); if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; - dev->freq = clamp(f->frequency, 87 * 16000U, 108 * 16000U); + dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U); pcm20_setfreq(dev, dev->freq); return 0; } diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c index 9c5a267..f9cdd8b 100644 --- a/drivers/media/radio/radio-mr800.c +++ b/drivers/media/radio/radio-mr800.c @@ -323,7 +323,7 @@ static int vidioc_s_tuner(struct file *file, void *priv, /* vidioc_s_frequency - set tuner radio frequency */ static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct amradio_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c index 637a555..6142b5b 100644 --- a/drivers/media/radio/radio-sf16fmi.c +++ b/drivers/media/radio/radio-sf16fmi.c @@ -151,7 +151,7 @@ static int vidioc_s_tuner(struct file *file, void *priv, } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct fmi *fmi = video_drvdata(file); diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 3df4339..2a2d21e 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -215,7 +215,7 @@ static int radio_si4713_g_frequency(struct file *file, void *p, } static int radio_si4713_s_frequency(struct file *file, void *p, - struct v4l2_frequency *vf) + const struct v4l2_frequency *vf) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, s_frequency, vf); diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c index 1978516..8938428 100644 --- a/drivers/media/radio/radio-tea5764.c +++ b/drivers/media/radio/radio-tea5764.c @@ -351,7 +351,7 @@ static int vidioc_s_tuner(struct file *file, void *priv, } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct tea5764_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c index 4b5190d..fcd7c19 100644 --- a/drivers/media/radio/radio-tea5777.c +++ b/drivers/media/radio/radio-tea5777.c @@ -368,7 +368,7 @@ static int vidioc_g_frequency(struct file *file, void *priv, } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct radio_tea5777 *tea = video_drvdata(file); diff --git a/drivers/media/radio/radio-timb.c b/drivers/media/radio/radio-timb.c index b87effe..1712c05 100644 --- a/drivers/media/radio/radio-timb.c +++ b/drivers/media/radio/radio-timb.c @@ -91,7 +91,7 @@ static int timbradio_vidioc_s_audio(struct file *file, void *priv, } static int timbradio_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct timbradio *tr = video_drvdata(file); return v4l2_subdev_call(tr->sd_tuner, tuner, s_frequency, f); diff --git a/drivers/media/radio/radio-wl1273.c b/drivers/media/radio/radio-wl1273.c index 02151e0..9a02fee 100644 --- a/drivers/media/radio/radio-wl1273.c +++ b/drivers/media/radio/radio-wl1273.c @@ -1640,7 +1640,7 @@ static int wl1273_fm_vidioc_g_frequency(struct file *file, void *priv, } static int wl1273_fm_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *freq) + const struct v4l2_frequency *freq) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); struct wl1273_core *core = radio->core; diff --git a/drivers/media/radio/si470x/radio-si470x-common.c b/drivers/media/radio/si470x/radio-si470x-common.c index 1898938..5708633 100644 --- a/drivers/media/radio/si470x/radio-si470x-common.c +++ b/drivers/media/radio/si470x/radio-si470x-common.c @@ -678,7 +678,7 @@ static int si470x_vidioc_g_frequency(struct file *file, void *priv, * si470x_vidioc_s_frequency - set tuner or modulator radio frequency */ static int si470x_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *freq) + const struct v4l2_frequency *freq) { struct si470x_device *radio = video_drvdata(file); int retval; diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c index bd61b3b..e305c14 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713-i2c.c @@ -1212,7 +1212,7 @@ exit: return rval; } -static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f); +static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f); static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulator *); /* * si4713_setup - Sets the device up with current configuration. @@ -1950,7 +1950,7 @@ unlock: } /* si4713_s_frequency - set tuner or modulator radio frequency */ -static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f) { struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; @@ -1970,7 +1970,6 @@ static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) rval = 0; } sdev->frequency = frequency; - f->frequency = si4713_to_v4l2(frequency); unlock: mutex_unlock(&sdev->mutex); diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c index b18c2dc..8673955 100644 --- a/drivers/media/radio/tef6862.c +++ b/drivers/media/radio/tef6862.c @@ -101,7 +101,7 @@ static int tef6862_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) return v->index ? -EINVAL : 0; } -static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +static int tef6862_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f) { struct tef6862_state *state = to_state(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.c b/drivers/media/radio/wl128x/fmdrv_v4l2.c index 0a8ee8f..0183956 100644 --- a/drivers/media/radio/wl128x/fmdrv_v4l2.c +++ b/drivers/media/radio/wl128x/fmdrv_v4l2.c @@ -388,7 +388,7 @@ static int fm_v4l2_vidioc_g_freq(struct file *file, void *priv, /* Set tuner or modulator radio frequency */ static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv, - struct v4l2_frequency *freq) + const struct v4l2_frequency *freq) { struct fmdev *fmdev = video_drvdata(file); @@ -396,9 +396,7 @@ static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv, * As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency * in units of 62.5 Hz. */ - freq->frequency = (u32)(freq->frequency / 16); - - return fmc_set_freq(fmdev, freq->frequency); + return fmc_set_freq(fmdev, freq->frequency / 16); } /* Set hardware frequency seek. If current mode is NOT RX, set it RX. */ -- cgit v1.1 From 2f73c7c582a685b3198b974cd6d964d0338f8ab5 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 15 Mar 2013 06:10:06 -0300 Subject: [media] v4l2: add const to argument of write-only s_tuner ioctl This ioctl is defined as IOW, so pass the argument as const. Signed-off-by: Hans Verkuil Acked-by: Laurent Pinchart Acked-by: Alexey Klimov Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/dsbr100.c | 2 +- drivers/media/radio/radio-cadet.c | 2 +- drivers/media/radio/radio-isa.c | 2 +- drivers/media/radio/radio-ma901.c | 2 +- drivers/media/radio/radio-miropcm20.c | 8 +++++--- drivers/media/radio/radio-mr800.c | 2 +- drivers/media/radio/radio-sf16fmi.c | 2 +- drivers/media/radio/radio-tea5764.c | 2 +- drivers/media/radio/radio-tea5777.c | 7 +++---- drivers/media/radio/radio-timb.c | 2 +- drivers/media/radio/radio-wl1273.c | 2 +- drivers/media/radio/si470x/radio-si470x-common.c | 2 +- drivers/media/radio/tef6862.c | 2 +- drivers/media/radio/wl128x/fmdrv_v4l2.c | 2 +- 14 files changed, 20 insertions(+), 19 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/dsbr100.c b/drivers/media/radio/dsbr100.c index e140a72..142c2ee 100644 --- a/drivers/media/radio/dsbr100.c +++ b/drivers/media/radio/dsbr100.c @@ -208,7 +208,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c index 59be293..545c04c 100644 --- a/drivers/media/radio/radio-cadet.c +++ b/drivers/media/radio/radio-cadet.c @@ -390,7 +390,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c index 0c1e27b..6ff3508 100644 --- a/drivers/media/radio/radio-isa.c +++ b/drivers/media/radio/radio-isa.c @@ -87,7 +87,7 @@ static int radio_isa_g_tuner(struct file *file, void *priv, } static int radio_isa_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct radio_isa_card *isa = video_drvdata(file); const struct radio_isa_ops *ops = isa->drv->ops; diff --git a/drivers/media/radio/radio-ma901.c b/drivers/media/radio/radio-ma901.c index 7f85c6f..18320c2 100644 --- a/drivers/media/radio/radio-ma901.c +++ b/drivers/media/radio/radio-ma901.c @@ -239,7 +239,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, /* vidioc_s_tuner - set tuner attributes */ static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct ma901radio_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c index 2b8d31d..a7e93d7 100644 --- a/drivers/media/radio/radio-miropcm20.c +++ b/drivers/media/radio/radio-miropcm20.c @@ -103,16 +103,18 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct pcm20 *dev = video_drvdata(file); if (v->index) return -EINVAL; if (v->audmode > V4L2_TUNER_MODE_STEREO) - v->audmode = V4L2_TUNER_MODE_STEREO; + dev->audmode = V4L2_TUNER_MODE_STEREO; + else + dev->audmode = v->audmode; snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, - v->audmode == V4L2_TUNER_MODE_MONO, -1); + dev->audmode == V4L2_TUNER_MODE_MONO, -1); return 0; } diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c index f9cdd8b..2ce75d1 100644 --- a/drivers/media/radio/radio-mr800.c +++ b/drivers/media/radio/radio-mr800.c @@ -305,7 +305,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, /* vidioc_s_tuner - set tuner attributes */ static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct amradio_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c index 6142b5b..adfcc61 100644 --- a/drivers/media/radio/radio-sf16fmi.c +++ b/drivers/media/radio/radio-sf16fmi.c @@ -145,7 +145,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c index 8938428..38d563d 100644 --- a/drivers/media/radio/radio-tea5764.c +++ b/drivers/media/radio/radio-tea5764.c @@ -339,7 +339,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct tea5764_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c index fcd7c19..e245597 100644 --- a/drivers/media/radio/radio-tea5777.c +++ b/drivers/media/radio/radio-tea5777.c @@ -336,7 +336,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct radio_tea5777 *tea = video_drvdata(file); u32 orig_audmode = tea->audmode; @@ -344,10 +344,9 @@ static int vidioc_s_tuner(struct file *file, void *priv, if (v->index) return -EINVAL; - if (v->audmode > V4L2_TUNER_MODE_STEREO) - v->audmode = V4L2_TUNER_MODE_STEREO; - tea->audmode = v->audmode; + if (tea->audmode > V4L2_TUNER_MODE_STEREO) + tea->audmode = V4L2_TUNER_MODE_STEREO; if (tea->audmode != orig_audmode && tea->band == BAND_FM) return radio_tea5777_set_freq(tea); diff --git a/drivers/media/radio/radio-timb.c b/drivers/media/radio/radio-timb.c index 1712c05..bb7b143 100644 --- a/drivers/media/radio/radio-timb.c +++ b/drivers/media/radio/radio-timb.c @@ -56,7 +56,7 @@ static int timbradio_vidioc_g_tuner(struct file *file, void *priv, } static int timbradio_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct timbradio *tr = video_drvdata(file); return v4l2_subdev_call(tr->sd_tuner, tuner, s_tuner, v); diff --git a/drivers/media/radio/radio-wl1273.c b/drivers/media/radio/radio-wl1273.c index 9a02fee..97c2c18 100644 --- a/drivers/media/radio/radio-wl1273.c +++ b/drivers/media/radio/radio-wl1273.c @@ -1559,7 +1559,7 @@ out: } static int wl1273_fm_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); struct wl1273_core *core = radio->core; diff --git a/drivers/media/radio/si470x/radio-si470x-common.c b/drivers/media/radio/si470x/radio-si470x-common.c index 5708633..5c57e5b 100644 --- a/drivers/media/radio/si470x/radio-si470x-common.c +++ b/drivers/media/radio/si470x/radio-si470x-common.c @@ -636,7 +636,7 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, * si470x_vidioc_s_tuner - set tuner attributes */ static int si470x_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct si470x_device *radio = video_drvdata(file); diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c index 8673955..82c6c94 100644 --- a/drivers/media/radio/tef6862.c +++ b/drivers/media/radio/tef6862.c @@ -96,7 +96,7 @@ static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) return 0; } -static int tef6862_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) +static int tef6862_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.c b/drivers/media/radio/wl128x/fmdrv_v4l2.c index 0183956..5dec323 100644 --- a/drivers/media/radio/wl128x/fmdrv_v4l2.c +++ b/drivers/media/radio/wl128x/fmdrv_v4l2.c @@ -331,7 +331,7 @@ static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv, * Should we set other tuner attributes, too? */ static int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct fmdev *fmdev = video_drvdata(file); u16 aud_mode; -- cgit v1.1 From 6d43be7789db0455a82a3ad4ff5f713cc588c1e2 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 26 Mar 2013 08:04:52 -0300 Subject: [media] ioctl numbers are unsigned int ioctl's number is unsigned int. Fix it at vidioc_default. Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 2a2d21e..38b3f15 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -222,7 +222,7 @@ static int radio_si4713_s_frequency(struct file *file, void *p, } static long radio_si4713_default(struct file *file, void *p, - bool valid_prio, int cmd, void *arg) + bool valid_prio, unsigned int cmd, void *arg) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, ioctl, cmd, arg); -- cgit v1.1 From 4b0cac5e759552a24dbdc940db268601422d5b0a Mon Sep 17 00:00:00 2001 From: Alexey Klimov Date: Thu, 14 Mar 2013 18:12:06 -0300 Subject: [media] radio-mr800: move clamp_t check inside amradio_set_freq() If i run verbose v4l2-compliance with my radio-mr800 device few times then i get warning about frequency out of range: root@machine:~# v4l2-compliance -r /dev/radio0 -v 2 is radio Driver Info: Driver name : radio-mr800 Card type : AverMedia MR 800 USB FM Radio Bus info : usb-0000:00:1a.0-1.2 Driver version: 3.9.0 Capabilities : 0x80050400 Tuner Radio Device Capabilities Device Caps : 0x00050400 Tuner Radio Compliance test for device /dev/radio0 (not using libv4l2): Required ioctls: test VIDIOC_QUERYCAP: OK Allow for multiple opens: test second radio open: OK test VIDIOC_QUERYCAP: OK test VIDIOC_G/S_PRIORITY: OK Debug ioctls: test VIDIOC_DBG_G_CHIP_IDENT: OK (Not Supported) test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported) test VIDIOC_LOG_STATUS: OK Input ioctls: test VIDIOC_G/S_TUNER: OK warn: v4l2-test-input-output.cpp(234): returned tuner 0 frequency out of range (6550200 not in [1400000...1728000]) test VIDIOC_G/S_FREQUENCY: OK test VIDIOC_S_HW_FREQ_SEEK: OK test VIDIOC_ENUMAUDIO: OK (Not Supported) test VIDIOC_G/S/ENUMINPUT: OK (Not Supported) test VIDIOC_G/S_AUDIO: OK (Not Supported) Inputs: 0 Audio Inputs: 0 Tuners: 1 Output ioctls: test VIDIOC_G/S_MODULATOR: OK (Not Supported) test VIDIOC_G/S_FREQUENCY: OK test VIDIOC_ENUMAUDOUT: OK (Not Supported) test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported) test VIDIOC_G/S_AUDOUT: OK (Not Supported) Outputs: 0 Audio Outputs: 0 Modulators: 0 Control ioctls: info: checking v4l2_queryctrl of control 'User Controls' (0x00980001) info: checking v4l2_queryctrl of control 'Mute' (0x00980909) info: checking v4l2_queryctrl of control 'Mute' (0x00980909) test VIDIOC_QUERYCTRL/MENU: OK info: checking control 'User Controls' (0x00980001) info: checking control 'Mute' (0x00980909) test VIDIOC_G/S_CTRL: OK info: checking extended control 'User Controls' (0x00980001) info: checking extended control 'Mute' (0x00980909) test VIDIOC_G/S/TRY_EXT_CTRLS: OK info: checking control event 'User Controls' (0x00980001) info: checking control event 'Mute' (0x00980909) test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK test VIDIOC_G/S_JPEGCOMP: OK (Not Supported) Standard Controls: 2 Private Controls: 0 Input/Output configuration ioctls: test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported) test VIDIOC_ENUM/G/S/QUERY_DV_PRESETS: OK (Not Supported) test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported) test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported) Format ioctls: test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported) test VIDIOC_G/S_PARM: OK (Not Supported) test VIDIOC_G_FBUF: OK (Not Supported) test VIDIOC_G_FMT: OK (Not Supported) test VIDIOC_TRY_FMT: OK (Not Supported) test VIDIOC_S_FMT: OK (Not Supported) test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported) Codec ioctls: test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported) test VIDIOC_G_ENC_INDEX: OK (Not Supported) test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported) Buffer ioctls: test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported) Total: 38, Succeeded: 38, Failed: 0, Warnings: 1 Some printk() debugging showed that vidioc_s_hw_freq_seek() setups radio->curfreq to out of range value (lines 395-396) and calls amradio_set_freq() to set this frequency on device without any out-of-range checks. Patch protects from setting up frequency on device to incorrect value moving clamp_t check inside amradio_set_freq. With this patch we can call amradio_set_freq() with out of range frequency from any place. Also put comment that sometimes radio->curfreq is set to out of range value in vidioc_s_hw_freq_seek(). Signed-off-by: Alexey Klimov Acked-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-mr800.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c index 2ce75d1..a360227 100644 --- a/drivers/media/radio/radio-mr800.c +++ b/drivers/media/radio/radio-mr800.c @@ -203,10 +203,14 @@ static int amradio_set_mute(struct amradio_device *radio, bool mute) /* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ static int amradio_set_freq(struct amradio_device *radio, int freq) { - unsigned short freq_send = 0x10 + (freq >> 3) / 25; + unsigned short freq_send; u8 buf[3]; int retval; + /* we need to be sure that frequency isn't out of range */ + freq = clamp_t(unsigned, freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); + freq_send = 0x10 + (freq >> 3) / 25; + /* frequency is calculated from freq_send and placed in first 2 bytes */ buf[0] = (freq_send >> 8) & 0xff; buf[1] = freq_send & 0xff; @@ -329,8 +333,7 @@ static int vidioc_s_frequency(struct file *file, void *priv, if (f->tuner != 0) return -EINVAL; - return amradio_set_freq(radio, clamp_t(unsigned, f->frequency, - FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL)); + return amradio_set_freq(radio, f->frequency); } /* vidioc_g_frequency - get tuner radio frequency */ @@ -389,6 +392,7 @@ static int vidioc_s_hw_freq_seek(struct file *file, void *priv, continue; amradio_send_cmd(radio, AMRADIO_GET_FREQ, 0, NULL, 0, true); if (radio->buffer[1] || radio->buffer[2]) { + /* To check: sometimes radio->curfreq is set to out of range value */ radio->curfreq = (radio->buffer[1] << 8) | radio->buffer[2]; radio->curfreq = (radio->curfreq - 0x10) * 200; amradio_send_cmd(radio, AMRADIO_STOP_SEARCH, -- cgit v1.1 From 30bac9110455402fa8888740c6819dd3daa2666f Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 26 Mar 2013 22:47:26 -0300 Subject: [media] v4l2: Add a V4L2 driver for SI476X MFD This commit adds a driver that exposes all the radio related functionality of the Si476x series of chips via the V4L2 subsystem. Acked-by: Hans Verkuil Signed-off-by: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/Kconfig | 17 + drivers/media/radio/Makefile | 1 + drivers/media/radio/radio-si476x.c | 1599 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1617 insertions(+) create mode 100644 drivers/media/radio/radio-si476x.c (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 24e64a0..28ded24 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,6 +18,23 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI476X + tristate "Silicon Laboratories Si476x I2C FM Radio" + depends on I2C && VIDEO_V4L2 + select MFD_CORE + select MFD_SI476X_CORE + select SND_SOC_SI476X + ---help--- + Choose Y here if you have this FM radio chip. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux 2 API. Information on + this API and pointers to "v4l2" programs may be found at + . + + To compile this driver as a module, choose M here: the + module will be called radio-si476x. + config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 303eaeb..0dcdb32 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o +obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c new file mode 100644 index 0000000..0895a0c --- /dev/null +++ b/drivers/media/radio/radio-si476x.c @@ -0,0 +1,1599 @@ +/* + * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FM_FREQ_RANGE_LOW 64000000 +#define FM_FREQ_RANGE_HIGH 108000000 + +#define AM_FREQ_RANGE_LOW 520000 +#define AM_FREQ_RANGE_HIGH 30000000 + +#define PWRLINEFLTR (1 << 8) + +#define FREQ_MUL (10000000 / 625) + +#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) + +#define DRIVER_NAME "si476x-radio" +#define DRIVER_CARD "SI476x AM/FM Receiver" + +enum si476x_freq_bands { + SI476X_BAND_FM, + SI476X_BAND_AM, +}; + +static const struct v4l2_frequency_band si476x_bands[] = { + [SI476X_BAND_FM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 64 * FREQ_MUL, + .rangehigh = 108 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_FM, + }, + [SI476X_BAND_AM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_AM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 0.52 * FREQ_MUL, + .rangehigh = 30 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) +{ + return freq >= si476x_bands[band].rangelow && + freq <= si476x_bands[band].rangehigh; +} + +static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, + int band) +{ + return low >= si476x_bands[band].rangelow && + high <= si476x_bands[band].rangehigh; +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); + +enum phase_diversity_modes_idx { + SI476X_IDX_PHDIV_DISABLED, + SI476X_IDX_PHDIV_PRIMARY_COMBINING, + SI476X_IDX_PHDIV_PRIMARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_COMBINING, +}; + +static const char * const phase_diversity_modes[] = { + [SI476X_IDX_PHDIV_DISABLED] = "Disabled", + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", +}; + +static inline enum phase_diversity_modes_idx +si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) +{ + switch (mode) { + default: /* FALLTHROUGH */ + case SI476X_PHDIV_DISABLED: + return SI476X_IDX_PHDIV_DISABLED; + case SI476X_PHDIV_PRIMARY_COMBINING: + return SI476X_IDX_PHDIV_PRIMARY_COMBINING; + case SI476X_PHDIV_PRIMARY_ANTENNA: + return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_ANTENNA: + return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_COMBINING: + return SI476X_IDX_PHDIV_SECONDARY_COMBINING; + } +} + +static inline enum si476x_phase_diversity_mode +si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) +{ + static const int idx_to_value[] = { + [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, + }; + + return idx_to_value[idx]; +} + +static const struct v4l2_ctrl_ops si476x_ctrl_ops = { + .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, + .s_ctrl = si476x_radio_s_ctrl, +}; + + +enum si476x_ctrl_idx { + SI476X_IDX_RSSI_THRESHOLD, + SI476X_IDX_SNR_THRESHOLD, + SI476X_IDX_MAX_TUNE_ERROR, + SI476X_IDX_HARMONICS_COUNT, + SI476X_IDX_DIVERSITY_MODE, + SI476X_IDX_INTERCHIP_LINK, +}; +static struct v4l2_ctrl_config si476x_ctrls[] = { + + /** + * SI476X during its station seeking(or tuning) process uses several + * parameters to detrmine if "the station" is valid: + * + * - Signal's SNR(in dBuV) must be lower than + * #V4L2_CID_SI476X_SNR_THRESHOLD + * - Signal's RSSI(in dBuV) must be greater than + * #V4L2_CID_SI476X_RSSI_THRESHOLD + * - Signal's frequency deviation(in units of 2ppm) must not be + * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR + */ + [SI476X_IDX_RSSI_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_RSSI_THRESHOLD, + .name = "Valid RSSI Threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_SNR_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_SNR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Valid SNR Threshold", + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_MAX_TUNE_ERROR] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Max Tune Errors", + .min = 0, + .max = 126 * 2, + .step = 2, + }, + + /** + * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics + * built-in power-line noise supression filter is to reject + * during AM-mode operation. + */ + [SI476X_IDX_HARMONICS_COUNT] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_HARMONICS_COUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + + .name = "Count of Harmonics to Reject", + .min = 0, + .max = 20, + .step = 1, + }, + + /** + * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which + * two tuners working in diversity mode are to work in. + * + * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled + * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is + * on, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is + * off, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is + * off, secondary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is + * on, secondary tuner's antenna is the main one. + */ + [SI476X_IDX_DIVERSITY_MODE] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_DIVERSITY_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Phase Diversity Mode", + .qmenu = phase_diversity_modes, + .min = 0, + .max = ARRAY_SIZE(phase_diversity_modes) - 1, + }, + + /** + * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in + * diversity mode indicator. Allows user to determine if two + * chips working in diversity mode have established a link + * between each other and if the system as a whole uses + * signals from both antennas to receive FM radio. + */ + [SI476X_IDX_INTERCHIP_LINK] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_INTERCHIP_LINK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, + .name = "Inter-Chip Link", + .min = 0, + .max = 1, + .step = 1, + }, +}; + +struct si476x_radio; + +/** + * struct si476x_radio_ops - vtable of tuner functions + * + * This table holds pointers to functions implementing particular + * operations depending on the mode in which the tuner chip was + * configured to start in. If the function is not supported + * corresponding element is set to #NULL. + * + * @tune_freq: Tune chip to a specific frequency + * @seek_start: Star station seeking + * @rsq_status: Get Recieved Signal Quality(RSQ) status + * @rds_blckcnt: Get recived RDS blocks count + * @phase_diversity: Change phase diversity mode of the tuner + * @phase_div_status: Get phase diversity mode status + * @acf_status: Get the status of Automatically Controlled + * Features(ACF) + * @agc_status: Get Automatic Gain Control(AGC) status + */ +struct si476x_radio_ops { + int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); + int (*seek_start)(struct si476x_core *, bool, bool); + int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + int (*rds_blckcnt)(struct si476x_core *, bool, + struct si476x_rds_blockcount_report *); + + int (*phase_diversity)(struct si476x_core *, + enum si476x_phase_diversity_mode); + int (*phase_div_status)(struct si476x_core *); + int (*acf_status)(struct si476x_core *, + struct si476x_acf_status_report *); + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); +}; + +/** + * struct si476x_radio - radio device + * + * @core: Pointer to underlying core device + * @videodev: Pointer to video device created by V4L2 subsystem + * @ops: Vtable of functions. See struct si476x_radio_ops for details + * @kref: Reference counter + * @core_lock: An r/w semaphore to brebvent the deletion of underlying + * core structure is the radio device is being used + */ +struct si476x_radio { + struct v4l2_device v4l2dev; + struct video_device videodev; + struct v4l2_ctrl_handler ctrl_handler; + + struct si476x_core *core; + /* This field should not be accesses unless core lock is held */ + const struct si476x_radio_ops *ops; + + struct dentry *debugfs; + u32 audmode; +}; + +static inline struct si476x_radio * +v4l2_dev_to_radio(struct v4l2_device *d) +{ + return container_of(d, struct si476x_radio, v4l2dev); +} + +static inline struct si476x_radio * +v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) +{ + return container_of(d, struct si476x_radio, ctrl_handler); +} + +/* + * si476x_vidioc_querycap - query device capabilities + */ +static int si476x_radio_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct si476x_radio *radio = video_drvdata(file); + + strlcpy(capability->driver, radio->v4l2dev.name, + sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", radio->v4l2dev.name); + + capability->device_caps = V4L2_CAP_TUNER + | V4L2_CAP_RADIO + | V4L2_CAP_HW_FREQ_SEEK; + + si476x_core_lock(radio->core); + if (!si476x_core_is_a_secondary_tuner(radio->core)) + capability->device_caps |= V4L2_CAP_RDS_CAPTURE + | V4L2_CAP_READWRITE; + si476x_core_unlock(radio->core); + + capability->capabilities = capability->device_caps + | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int si476x_radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (band->tuner != 0) + return -EINVAL; + + switch (radio->core->chip_id) { + /* AM/FM tuners -- all bands are supported */ + case SI476X_CHIP_SI4761: + case SI476X_CHIP_SI4764: + if (band->index < ARRAY_SIZE(si476x_bands)) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + /* FM companion tuner chips -- only FM bands are + * supported */ + case SI476X_CHIP_SI4768: + if (band->index == SI476X_BAND_FM) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int err; + struct si476x_rsq_status_report report; + struct si476x_radio *radio = video_drvdata(file); + + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + if (tuner->index != 0) + return -EINVAL; + + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies + * in multiples of + * 62.5 Hz */ + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_HWSEEK_BOUNDED + | V4L2_TUNER_CAP_HWSEEK_WRAP + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + + si476x_core_lock(radio->core); + + if (si476x_core_is_a_secondary_tuner(radio->core)) { + strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); + tuner->rxsubchans = 0; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } else if (si476x_core_has_am(radio->core)) { + if (si476x_core_is_a_primary_tuner(radio->core)) + strlcpy(tuner->name, "AM/FM (primary)", + sizeof(tuner->name)); + else + strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); + + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO + | V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + + tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; + } else { + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } + + tuner->audmode = radio->audmode; + + tuner->afc = 1; + tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; + + err = radio->ops->rsq_status(radio->core, + &args, &report); + if (err < 0) { + tuner->signal = 0; + } else { + /* + * tuner->signal value range: 0x0000 .. 0xFFFF, + * report.rssi: -128 .. 127 + */ + tuner->signal = (report.rssi + 128) * 257; + } + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct si476x_radio *radio = video_drvdata(file); + + if (tuner->index != 0) + return -EINVAL; + + if (tuner->audmode == V4L2_TUNER_MODE_MONO || + tuner->audmode == V4L2_TUNER_MODE_STEREO) + radio->audmode = tuner->audmode; + else + radio->audmode = V4L2_TUNER_MODE_STEREO; + + return 0; +} + +static int si476x_radio_init_vtable(struct si476x_radio *radio, + enum si476x_func func) +{ + static const struct si476x_radio_ops fm_ops = { + .tune_freq = si476x_core_cmd_fm_tune_freq, + .seek_start = si476x_core_cmd_fm_seek_start, + .rsq_status = si476x_core_cmd_fm_rsq_status, + .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, + .phase_diversity = si476x_core_cmd_fm_phase_diversity, + .phase_div_status = si476x_core_cmd_fm_phase_div_status, + .acf_status = si476x_core_cmd_fm_acf_status, + .agc_status = si476x_core_cmd_agc_status, + }; + + static const struct si476x_radio_ops am_ops = { + .tune_freq = si476x_core_cmd_am_tune_freq, + .seek_start = si476x_core_cmd_am_seek_start, + .rsq_status = si476x_core_cmd_am_rsq_status, + .rds_blckcnt = NULL, + .phase_diversity = NULL, + .phase_div_status = NULL, + .acf_status = si476x_core_cmd_am_acf_status, + .agc_status = NULL, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + radio->ops = &fm_ops; + return 0; + + case SI476X_FUNC_AM_RECEIVER: + radio->ops = &am_ops; + return 0; + default: + WARN(1, "Unexpected tuner function value\n"); + return -EINVAL; + } +} + +static int si476x_radio_pretune(struct si476x_radio *radio, + enum si476x_func func) +{ + int retval; + + struct si476x_tune_freq_args args = { + .zifsr = false, + .hd = false, + .injside = SI476X_INJSIDE_AUTO, + .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, + .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, + .antcap = 0, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 92 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + case SI476X_FUNC_AM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 0.6 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + default: + WARN(1, "Unexpected tuner function value\n"); + retval = -EINVAL; + } + + return retval; +} +static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + + /* regcache_mark_dirty(radio->core->regmap); */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, + SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + SI476X_PROP_AUDIO_PWR_LINE_FILTER); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_PROP_INT_CTL_ENABLE); + if (err < 0) + return err; + + /* + * Is there any point in restoring SNR and the like + * when switching between AM/FM? + */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + SI476X_PROP_VALID_MAX_TUNE_ERROR); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + SI476X_PROP_VALID_RSSI_THRESHOLD); + if (err < 0) + return err; + + if (func == SI476X_FUNC_FM_RECEIVER) { + if (si476x_core_has_diversity(radio->core)) { + err = si476x_core_cmd_fm_phase_diversity(radio->core, + radio->core->diversity_mode); + if (err < 0) + return err; + } + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_PROP_FM_RDS_CONFIG); + if (err < 0) + return err; + } + + return si476x_radio_init_vtable(radio, func); + +} + +static int si476x_radio_change_func(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + bool soft; + /* + * Since power/up down is a very time consuming operation, + * try to avoid doing it if the requested mode matches the one + * the tuner is in + */ + if (func == radio->core->power_up_parameters.func) + return 0; + + soft = true; + err = si476x_core_stop(radio->core, soft); + if (err < 0) { + /* + * OK, if the chip does not want to play nice let's + * try to reset it in more brutal way + */ + soft = false; + err = si476x_core_stop(radio->core, soft); + if (err < 0) + return err; + } + /* + Set the desired radio tuner function + */ + radio->core->power_up_parameters.func = func; + + err = si476x_core_start(radio->core, soft); + if (err < 0) + return err; + + /* + * No need to do the rest of manipulations for the bootlader + * mode + */ + if (func != SI476X_FUNC_FM_RECEIVER && + func != SI476X_FUNC_AM_RECEIVER) + return err; + + return si476x_radio_do_post_powerup_init(radio, func); +} + +static int si476x_radio_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (radio->ops->rsq_status) { + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = true, + .cancel = false, + .stcack = false, + }; + + err = radio->ops->rsq_status(radio->core, &args, &report); + if (!err) + f->frequency = si476x_to_v4l2(radio->core, + report.readfreq); + } else { + err = -EINVAL; + } + + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_tune_freq_args args; + struct si476x_radio *radio = video_drvdata(file); + + const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + + si476x_bands[SI476X_BAND_FM].rangelow) / 2; + const int band = (f->frequency > midrange) ? + SI476X_BAND_FM : SI476X_BAND_AM; + const enum si476x_func func = (band == SI476X_BAND_AM) ? + SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + f->frequency = clamp(f->frequency, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); + + if (si476x_radio_freq_is_inside_of_the_band(f->frequency, + SI476X_BAND_AM) && + (!si476x_core_has_am(radio->core) || + si476x_core_is_a_secondary_tuner(radio->core))) { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + args.zifsr = false; + args.hd = false; + args.injside = SI476X_INJSIDE_AUTO; + args.freq = v4l2_to_si476x(radio->core, + f->frequency); + args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; + args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; + args.antcap = 0; + + err = radio->ops->tune_freq(radio->core, &args); + +unlock: + si476x_core_unlock(radio->core); + return err; +} + +static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + int err; + enum si476x_func func; + u32 rangelow, rangehigh; + struct si476x_radio *radio = video_drvdata(file); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (seek->tuner != 0 || + seek->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (!seek->rangelow) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + &rangelow); + if (!err) + rangelow = si476x_to_v4l2(radio->core, rangelow); + else + goto unlock; + } + if (!seek->rangehigh) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + &rangehigh); + if (!err) + rangehigh = si476x_to_v4l2(radio->core, rangehigh); + else + goto unlock; + } + + if (rangelow > rangehigh) { + err = -EINVAL; + goto unlock; + } + + if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_FM)) { + func = SI476X_FUNC_FM_RECEIVER; + + } else if (si476x_core_has_am(radio->core) && + si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_AM)) { + func = SI476X_FUNC_AM_RECEIVER; + } else { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + if (seek->rangehigh) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + v4l2_to_si476x(radio->core, + seek->rangehigh)); + if (err) + goto unlock; + } + if (seek->rangelow) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + v4l2_to_si476x(radio->core, + seek->rangelow)); + if (err) + goto unlock; + } + if (seek->spacing) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_FREQUENCY_SPACING, + v4l2_to_si476x(radio->core, + seek->spacing)); + if (err) + goto unlock; + } + + err = radio->ops->seek_start(radio->core, + seek->seek_upward, + seek->wrap_around); +unlock: + si476x_core_unlock(radio->core); + + + + return err; +} + +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_INTERCHIP_LINK: + if (si476x_core_has_diversity(radio->core)) { + if (radio->ops->phase_diversity) { + retval = radio->ops->phase_div_status(radio->core); + if (retval < 0) + break; + + ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); + retval = 0; + break; + } else { + retval = -ENOTTY; + break; + } + } + retval = -EINVAL; + break; + default: + retval = -EINVAL; + break; + } + si476x_core_unlock(radio->core); + return retval; + +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + enum si476x_phase_diversity_mode mode; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_HARMONICS_COUNT: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_HARMONICS_MASK, + ctrl->val); + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + switch (ctrl->val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_ENABLE_MASK, + 0); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_50HZ); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_60HZ); + break; + default: + retval = -EINVAL; + break; + } + break; + case V4L2_CID_SI476X_RSSI_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_RSSI_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_SNR_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_MAX_TUNE_ERROR: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + ctrl->val); + break; + case V4L2_CID_RDS_RECEPTION: + /* + * It looks like RDS related properties are + * inaccesable when tuner is in AM mode, so cache the + * changes + */ + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, true); + + if (ctrl->val) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, + radio->core->rds_fifo_depth); + if (retval < 0) + break; + + if (radio->core->client->irq) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_RDSRECV); + if (retval < 0) + break; + } + + /* Drain RDS FIFO before enabling RDS processing */ + retval = si476x_core_cmd_fm_rds_status(radio->core, + false, + true, + true, + NULL); + if (retval < 0) + break; + + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + SI476X_PROP_RDSEN); + } else { + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + !SI476X_PROP_RDSEN); + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, false); + break; + case V4L2_CID_TUNE_DEEMPHASIS: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + ctrl->val); + break; + + case V4L2_CID_SI476X_DIVERSITY_MODE: + mode = si476x_phase_diversity_idx_to_mode(ctrl->val); + + if (mode == radio->core->diversity_mode) { + retval = 0; + break; + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) { + /* + * Diversity cannot be configured while tuner + * is in AM mode so save the changes and carry on. + */ + radio->core->diversity_mode = mode; + retval = 0; + } else { + retval = radio->ops->phase_diversity(radio->core, mode); + if (!retval) + radio->core->diversity_mode = mode; + } + break; + + default: + retval = -EINVAL; + break; + } + + si476x_core_unlock(radio->core); + + return retval; +} + +static int si476x_radio_g_chip_ident(struct file *file, void *fh, + struct v4l2_dbg_chip_ident *chip) +{ + if (chip->match.type == V4L2_CHIP_MATCH_HOST && + v4l2_chip_match_host(&chip->match)) + return 0; + return -EINVAL; +} + + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int si476x_radio_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + int err; + unsigned int value; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + reg->size = 2; + err = regmap_read(radio->core->regmap, + (unsigned int)reg->reg, &value); + reg->val = value; + si476x_core_unlock(radio->core); + + return err; +} +static int si476x_radio_s_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + + int err; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + err = regmap_write(radio->core->regmap, + (unsigned int)reg->reg, + (unsigned int)reg->val); + si476x_core_unlock(radio->core); + + return err; +} +#endif + +static int si476x_radio_fops_open(struct file *file) +{ + struct si476x_radio *radio = video_drvdata(file); + int err; + + err = v4l2_fh_open(file); + if (err) + return err; + + if (v4l2_fh_is_singular_file(file)) { + si476x_core_lock(radio->core); + err = si476x_core_set_power_state(radio->core, + SI476X_POWER_UP_FULL); + if (err < 0) + goto done; + + err = si476x_radio_do_post_powerup_init(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + err = si476x_radio_pretune(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + si476x_core_unlock(radio->core); + /*Must be done after si476x_core_unlock to prevent a deadlock*/ + v4l2_ctrl_handler_setup(&radio->ctrl_handler); + } + + return err; + +power_down: + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); +done: + si476x_core_unlock(radio->core); + v4l2_fh_release(file); + + return err; +} + +static int si476x_radio_fops_release(struct file *file) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (v4l2_fh_is_singular_file(file) && + atomic_read(&radio->core->is_alive)) + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); + + err = v4l2_fh_release(file); + + return err; +} + +static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rval; + size_t fifo_len; + unsigned int copied; + + struct si476x_radio *radio = video_drvdata(file); + + /* block if no new data available */ + if (kfifo_is_empty(&radio->core->rds_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + rval = wait_event_interruptible(radio->core->rds_read_queue, + (!kfifo_is_empty(&radio->core->rds_fifo) || + !atomic_read(&radio->core->is_alive))); + if (rval < 0) + return -EINTR; + + if (!atomic_read(&radio->core->is_alive)) + return -ENODEV; + } + + fifo_len = kfifo_len(&radio->core->rds_fifo); + + if (kfifo_to_user(&radio->core->rds_fifo, buf, + min(fifo_len, count), + &copied) != 0) { + dev_warn(&radio->videodev.dev, + "Error during FIFO to userspace copy\n"); + rval = -EIO; + } else { + rval = (ssize_t)copied; + } + + return rval; +} + +static unsigned int si476x_radio_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct si476x_radio *radio = video_drvdata(file); + unsigned long req_events = poll_requested_events(pts); + unsigned int err = v4l2_ctrl_poll(file, pts); + + if (req_events & (POLLIN | POLLRDNORM)) { + if (atomic_read(&radio->core->is_alive)) + poll_wait(file, &radio->core->rds_read_queue, pts); + + if (!atomic_read(&radio->core->is_alive)) + err = POLLHUP; + + if (!kfifo_is_empty(&radio->core->rds_fifo)) + err = POLLIN | POLLRDNORM; + } + + return err; +} + +static const struct v4l2_file_operations si476x_fops = { + .owner = THIS_MODULE, + .read = si476x_radio_fops_read, + .poll = si476x_radio_fops_poll, + .unlocked_ioctl = video_ioctl2, + .open = si476x_radio_fops_open, + .release = si476x_radio_fops_release, +}; + + +static const struct v4l2_ioctl_ops si4761_ioctl_ops = { + .vidioc_querycap = si476x_radio_querycap, + .vidioc_g_tuner = si476x_radio_g_tuner, + .vidioc_s_tuner = si476x_radio_s_tuner, + + .vidioc_g_frequency = si476x_radio_g_frequency, + .vidioc_s_frequency = si476x_radio_s_frequency, + .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_g_chip_ident = si476x_radio_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = si476x_radio_g_register, + .vidioc_s_register = si476x_radio_s_register, +#endif +}; + + +static const struct video_device si476x_viddev_template = { + .fops = &si476x_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, +}; + + + +static ssize_t si476x_radio_read_acf_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_acf_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->acf_status) + err = radio->ops->acf_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_acf_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_acf_blob, +}; + +static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rds_blockcount_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rds_blckcnt(radio->core, true, + &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rds_blckcnt_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rds_blckcnt_blob, +}; + +static ssize_t si476x_radio_read_agc_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_agc_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->agc_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_agc_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_agc_blob, +}; + +static ssize_t si476x_radio_read_rsq_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_blob, +}; + +static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = true, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_primary_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_primary_blob, +}; + + +static int si476x_radio_init_debugfs(struct si476x_radio *radio) +{ + struct dentry *dentry; + int ret; + + dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto exit; + } + radio->debugfs = dentry; + + dentry = debugfs_create_file("acf", S_IRUGO, + radio->debugfs, radio, &radio_acf_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, + radio->debugfs, radio, + &radio_rds_blckcnt_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("agc", S_IRUGO, + radio->debugfs, radio, &radio_agc_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq", S_IRUGO, + radio->debugfs, radio, &radio_rsq_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq_primary", S_IRUGO, + radio->debugfs, radio, + &radio_rsq_primary_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + return 0; +cleanup: + debugfs_remove_recursive(radio->debugfs); +exit: + return ret; +} + + +static int si476x_radio_add_new_custom(struct si476x_radio *radio, + enum si476x_ctrl_idx idx) +{ + int rval; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, + &si476x_ctrls[idx], + NULL); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) + dev_err(radio->v4l2dev.dev, + "Could not initialize '%s' control %d\n", + si476x_ctrls[idx].name, rval); + + return rval; +} + +static int si476x_radio_probe(struct platform_device *pdev) +{ + int rval; + struct si476x_radio *radio; + struct v4l2_ctrl *ctrl; + + static atomic_t instance = ATOMIC_INIT(0); + + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->core = i2c_mfd_cell_to_core(&pdev->dev); + + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); + + rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); + if (rval) { + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); + return rval; + } + + memcpy(&radio->videodev, &si476x_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = &radio->v4l2dev; + radio->videodev.ioctl_ops = &si4761_ioctl_ops; + + video_set_drvdata(&radio->videodev, radio); + platform_set_drvdata(pdev, radio); + + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); + + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; + v4l2_ctrl_handler_init(&radio->ctrl_handler, + 1 + ARRAY_SIZE(si476x_ctrls)); + + if (si476x_core_has_am(radio->core)) { + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", + rval); + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, + SI476X_IDX_HARMONICS_COUNT); + if (rval < 0) + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); + if (rval < 0) + goto exit; + + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_TUNE_DEEMPHASIS, + V4L2_DEEMPHASIS_75_uS, 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", + rval); + goto exit; + } + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_RDS_RECEPTION, + 0, 1, 1, 1); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", + rval); + goto exit; + } + + if (si476x_core_has_diversity(radio->core)) { + si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = + si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); + si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); + if (rval < 0) + goto exit; + + si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); + if (rval < 0) + goto exit; + } + + /* register video device */ + rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); + if (rval < 0) { + dev_err(&pdev->dev, "Could not register video device\n"); + goto exit; + } + + rval = si476x_radio_init_debugfs(radio); + if (rval < 0) { + dev_err(&pdev->dev, "Could not creat debugfs interface\n"); + goto exit; + } + + return 0; +exit: + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + return rval; +} + +static int si476x_radio_remove(struct platform_device *pdev) +{ + struct si476x_radio *radio = platform_get_drvdata(pdev); + + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2dev); + debugfs_remove_recursive(radio->debugfs); + + return 0; +} + +MODULE_ALIAS("platform:si476x-radio"); + +static struct platform_driver si476x_radio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = si476x_radio_probe, + .remove = si476x_radio_remove, +}; +module_platform_driver(si476x_radio_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); +MODULE_LICENSE("GPL"); -- cgit v1.1 From 38a46c2128ade2a0c6ee4438297180b09a01c309 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Fri, 29 Mar 2013 09:43:14 -0300 Subject: [media] radio-si476x: vidioc_s* now uses a const parameter vidioc_s_tuner, vidioc_s_frequency and vidioc_s_register now uses a constant argument. So, the driver reports warnings: drivers/media/radio/radio-si476x.c:1196:2: warning: initialization from incompatible pointer type [enabled by default] drivers/media/radio/radio-si476x.c:1196:2: warning: (near initialization for 'si4761_ioctl_ops.vidioc_s_tuner') [enabled by default] drivers/media/radio/radio-si476x.c:1199:2: warning: initialization from incompatible pointer type [enabled by default] drivers/media/radio/radio-si476x.c:1199:2: warning: (near initialization for 'si4761_ioctl_ops.vidioc_s_frequency') [enabled by default] drivers/media/radio/radio-si476x.c:1209:2: warning: initialization from incompatible pointer type [enabled by default] drivers/media/radio/radio-si476x.c:1209:2: warning: (near initialization for 'si4761_ioctl_ops.vidioc_s_register') [enabled by default] This is due to a (soft) merge conflict, as both this driver and the const patches were applied for the same Kernel version. Cc: Hans Verkuil Cc: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si476x.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c index 0895a0c..9430c6a 100644 --- a/drivers/media/radio/radio-si476x.c +++ b/drivers/media/radio/radio-si476x.c @@ -472,7 +472,7 @@ static int si476x_radio_g_tuner(struct file *file, void *priv, } static int si476x_radio_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct si476x_radio *radio = video_drvdata(file); @@ -699,15 +699,16 @@ static int si476x_radio_g_frequency(struct file *file, void *priv, } static int si476x_radio_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { int err; + u32 freq = f->frequency; struct si476x_tune_freq_args args; struct si476x_radio *radio = video_drvdata(file); const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + si476x_bands[SI476X_BAND_FM].rangelow) / 2; - const int band = (f->frequency > midrange) ? + const int band = (freq > midrange) ? SI476X_BAND_FM : SI476X_BAND_AM; const enum si476x_func func = (band == SI476X_BAND_AM) ? SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; @@ -718,11 +719,11 @@ static int si476x_radio_s_frequency(struct file *file, void *priv, si476x_core_lock(radio->core); - f->frequency = clamp(f->frequency, - si476x_bands[band].rangelow, - si476x_bands[band].rangehigh); + freq = clamp(freq, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); - if (si476x_radio_freq_is_inside_of_the_band(f->frequency, + if (si476x_radio_freq_is_inside_of_the_band(freq, SI476X_BAND_AM) && (!si476x_core_has_am(radio->core) || si476x_core_is_a_secondary_tuner(radio->core))) { @@ -737,8 +738,7 @@ static int si476x_radio_s_frequency(struct file *file, void *priv, args.zifsr = false; args.hd = false; args.injside = SI476X_INJSIDE_AUTO; - args.freq = v4l2_to_si476x(radio->core, - f->frequency); + args.freq = v4l2_to_si476x(radio->core, freq); args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; args.antcap = 0; @@ -1046,7 +1046,7 @@ static int si476x_radio_g_register(struct file *file, void *fh, return err; } static int si476x_radio_s_register(struct file *file, void *fh, - struct v4l2_dbg_register *reg) + const struct v4l2_dbg_register *reg) { int err; -- cgit v1.1 From 6bf7861fa2bb4be3cc70a6e9aed664ce65270027 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 29 Mar 2013 17:22:24 -0300 Subject: [media] si476x: Fix some config dependencies and a compile warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit radio-si476x depends on SND and SND_SOC, the mfd driver should select REGMAP_I2C. Also fix a small compile warning in a debug message: drivers/mfd/si476x-i2c.c: In function ‘si476x_core_drain_rds_fifo’: drivers/mfd/si476x-i2c.c:391:4: warning: field width specifier ‘*’ expects argument of type ‘int’, but argument 4 has type ‘long unsigned int’ [-Wformat] Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 28ded24..fef427e 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -20,7 +20,7 @@ source "drivers/media/radio/si470x/Kconfig" config RADIO_SI476X tristate "Silicon Laboratories Si476x I2C FM Radio" - depends on I2C && VIDEO_V4L2 + depends on I2C && VIDEO_V4L2 && SND && SND_SOC select MFD_CORE select MFD_SI476X_CORE select SND_SOC_SI476X -- cgit v1.1 From 91e20e81087c990f3f9c0074ed740e63c666071b Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 29 Jan 2013 08:52:19 -0300 Subject: [media] radio-si4713: remove audout ioctls The audout ioctls are not appropriate for radio transmitters, they apply to video output devices only. Remove them from this driver. Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 32 -------------------------------- 1 file changed, 32 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 38b3f15..320f301 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -59,35 +59,6 @@ static const struct v4l2_file_operations radio_si4713_fops = { }; /* Video4Linux Interface */ -static int radio_si4713_fill_audout(struct v4l2_audioout *vao) -{ - /* TODO: check presence of audio output */ - strlcpy(vao->name, "FM Modulator Audio Out", 32); - - return 0; -} - -static int radio_si4713_enumaudout(struct file *file, void *priv, - struct v4l2_audioout *vao) -{ - return radio_si4713_fill_audout(vao); -} - -static int radio_si4713_g_audout(struct file *file, void *priv, - struct v4l2_audioout *vao) -{ - int rval = radio_si4713_fill_audout(vao); - - vao->index = 0; - - return rval; -} - -static int radio_si4713_s_audout(struct file *file, void *priv, - const struct v4l2_audioout *vao) -{ - return vao->index ? -EINVAL : 0; -} /* radio_si4713_querycap - query device capabilities */ static int radio_si4713_querycap(struct file *file, void *priv, @@ -229,9 +200,6 @@ static long radio_si4713_default(struct file *file, void *p, } static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { - .vidioc_enumaudout = radio_si4713_enumaudout, - .vidioc_g_audout = radio_si4713_g_audout, - .vidioc_s_audout = radio_si4713_s_audout, .vidioc_querycap = radio_si4713_querycap, .vidioc_queryctrl = radio_si4713_queryctrl, .vidioc_g_ext_ctrls = radio_si4713_g_ext_ctrls, -- cgit v1.1 From fdf51599c0fca3dc1e45924b71e09a862f34ec78 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 8 Apr 2013 06:11:12 -0300 Subject: [media] radio-si4713: embed struct video_device instead of allocating it Also set the v4l2_dev pointer in struct video_device as this was missing. Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 320f301..70dc652 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -48,7 +48,7 @@ MODULE_ALIAS("platform:radio-si4713"); /* Driver state struct */ struct radio_si4713_device { struct v4l2_device v4l2_dev; - struct video_device *radio_dev; + struct video_device radio_dev; }; /* radio_si4713_fops - file operations interface */ @@ -217,7 +217,7 @@ static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { static struct video_device radio_si4713_vdev_template = { .fops = &radio_si4713_fops, .name = "radio-si4713", - .release = video_device_release, + .release = video_device_release_empty, .ioctl_ops = &radio_si4713_ioctl_ops, .vfl_dir = VFL_DIR_TX, }; @@ -267,27 +267,18 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) goto put_adapter; } - rsdev->radio_dev = video_device_alloc(); - if (!rsdev->radio_dev) { - dev_err(&pdev->dev, "Failed to alloc video device.\n"); - rval = -ENOMEM; - goto put_adapter; - } - - memcpy(rsdev->radio_dev, &radio_si4713_vdev_template, - sizeof(radio_si4713_vdev_template)); - video_set_drvdata(rsdev->radio_dev, rsdev); - if (video_register_device(rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) { + rsdev->radio_dev = radio_si4713_vdev_template; + rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev; + video_set_drvdata(&rsdev->radio_dev, rsdev); + if (video_register_device(&rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) { dev_err(&pdev->dev, "Could not register video device.\n"); rval = -EIO; - goto free_vdev; + goto put_adapter; } dev_info(&pdev->dev, "New device successfully probed\n"); goto exit; -free_vdev: - video_device_release(rsdev->radio_dev); put_adapter: i2c_put_adapter(adapter); unregister_v4l2_dev: @@ -306,7 +297,7 @@ static int radio_si4713_pdriver_remove(struct platform_device *pdev) struct radio_si4713_device *rsdev; rsdev = container_of(v4l2_dev, struct radio_si4713_device, v4l2_dev); - video_unregister_device(rsdev->radio_dev); + video_unregister_device(&rsdev->radio_dev); i2c_put_adapter(client->adapter); v4l2_device_unregister(&rsdev->v4l2_dev); -- cgit v1.1 From a58841d0dcb79aebc558a1b55d29995f62aa61a4 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 8 Apr 2013 06:27:43 -0300 Subject: [media] radio-si4713: improve querycap Set bus_info and fill in device_caps. Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 70dc652..f0f0a90 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -67,7 +67,10 @@ static int radio_si4713_querycap(struct file *file, void *priv, strlcpy(capability->driver, "radio-si4713", sizeof(capability->driver)); strlcpy(capability->card, "Silicon Labs Si4713 Modulator", sizeof(capability->card)); - capability->capabilities = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; + strlcpy(capability->bus_info, "platform:radio-si4713", + sizeof(capability->bus_info)); + capability->device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; + capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } -- cgit v1.1 From 55b2a312c29695520f813e61d668318169895ba7 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 8 Apr 2013 06:31:30 -0300 Subject: [media] radio-si4713: use V4L2 core lock Simplify locking by using the V4L2 core lock mechanism. This allows us to remove all locking from the i2c module. This will also simplify the upcoming conversion to the control framework. Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 4 + drivers/media/radio/si4713-i2c.c | 156 +++++++++---------------------------- drivers/media/radio/si4713-i2c.h | 1 - 3 files changed, 40 insertions(+), 121 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index f0f0a90..633c545 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -49,6 +49,7 @@ MODULE_ALIAS("platform:radio-si4713"); struct radio_si4713_device { struct v4l2_device v4l2_dev; struct video_device radio_dev; + struct mutex lock; }; /* radio_si4713_fops - file operations interface */ @@ -247,6 +248,7 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) rval = -ENOMEM; goto exit; } + mutex_init(&rsdev->lock); rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev); if (rval) { @@ -272,6 +274,8 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) rsdev->radio_dev = radio_si4713_vdev_template; rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev; + /* Serialize all access to the si4713 */ + rsdev->radio_dev.lock = &rsdev->lock; video_set_drvdata(&rsdev->radio_dev, rsdev); if (video_register_device(&rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) { dev_err(&pdev->dev, "Could not register video device.\n"); diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c index e305c14..1cb9a2e 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713-i2c.c @@ -21,7 +21,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include #include #include #include @@ -458,15 +457,13 @@ static int si4713_checkrev(struct si4713_device *sdev) int rval; u8 resp[SI4713_GETREV_NRESP]; - mutex_lock(&sdev->mutex); - rval = si4713_send_command(sdev, SI4713_CMD_GET_REV, NULL, 0, resp, ARRAY_SIZE(resp), DEFAULT_TIMEOUT); if (rval < 0) - goto unlock; + return rval; if (resp[1] == SI4713_PRODUCT_NUMBER) { v4l2_info(&sdev->sd, "chip found @ 0x%02x (%s)\n", @@ -475,9 +472,6 @@ static int si4713_checkrev(struct si4713_device *sdev) v4l2_err(&sdev->sd, "Invalid product number\n"); rval = -EINVAL; } - -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -778,17 +772,9 @@ static int si4713_tx_rds_ps(struct si4713_device *sdev, u8 psid, static int si4713_set_power_state(struct si4713_device *sdev, u8 value) { - int rval; - - mutex_lock(&sdev->mutex); - if (value) - rval = si4713_powerup(sdev); - else - rval = si4713_powerdown(sdev); - - mutex_unlock(&sdev->mutex); - return rval; + return si4713_powerup(sdev); + return si4713_powerdown(sdev); } static int si4713_set_mute(struct si4713_device *sdev, u16 mute) @@ -797,8 +783,6 @@ static int si4713_set_mute(struct si4713_device *sdev, u16 mute) mute = set_mute(mute); - mutex_lock(&sdev->mutex); - if (sdev->power_state) rval = si4713_write_property(sdev, SI4713_TX_LINE_INPUT_MUTE, mute); @@ -806,8 +790,6 @@ static int si4713_set_mute(struct si4713_device *sdev, u16 mute) if (rval >= 0) sdev->mute = get_mute(mute); - mutex_unlock(&sdev->mutex); - return rval; } @@ -820,15 +802,13 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name) if (!strlen(ps_name)) memset(ps_name, 0, MAX_RDS_PS_NAME + 1); - mutex_lock(&sdev->mutex); - if (sdev->power_state) { /* Write the new ps name and clear the padding */ for (i = 0; i < MAX_RDS_PS_NAME; i += (RDS_BLOCK / 2)) { rval = si4713_tx_rds_ps(sdev, (i / (RDS_BLOCK / 2)), ps_name + i); if (rval < 0) - goto unlock; + return rval; } /* Setup the size to be sent */ @@ -841,19 +821,16 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name) SI4713_TX_RDS_PS_MESSAGE_COUNT, rds_ps_nblocks(len)); if (rval < 0) - goto unlock; + return rval; rval = si4713_write_property(sdev, SI4713_TX_RDS_PS_REPEAT_COUNT, DEFAULT_RDS_PS_REPEAT_COUNT * 2); if (rval < 0) - goto unlock; + return rval; } strncpy(sdev->rds_info.ps_name, ps_name, MAX_RDS_PS_NAME); - -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -864,14 +841,12 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) u8 b_index = 0, cr_inserted = 0; s8 left; - mutex_lock(&sdev->mutex); - if (!sdev->power_state) goto copy; rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_CLEAR, 0, 0, 0, &left); if (rval < 0) - goto unlock; + return rval; if (!strlen(rt)) goto copy; @@ -898,7 +873,7 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) compose_u16(rt[t_index + 2], rt[t_index + 3]), &left); if (rval < 0) - goto unlock; + return rval; t_index += RDS_RADIOTEXT_BLK_SIZE; @@ -908,9 +883,6 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) copy: strncpy(sdev->rds_info.radio_text, rt, MAX_RDS_RADIO_TEXT); - -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1114,9 +1086,7 @@ static int si4713_write_econtrol_tune(struct si4713_device *sdev, rval = validate_range(&sdev->sd, control); if (rval < 0) - goto exit; - - mutex_lock(&sdev->mutex); + return rval; switch (control->id) { case V4L2_CID_TUNE_POWER_LEVEL: @@ -1128,8 +1098,7 @@ static int si4713_write_econtrol_tune(struct si4713_device *sdev, antcap = control->value; break; default: - rval = -EINVAL; - goto unlock; + return -EINVAL; } if (sdev->power_state) @@ -1140,9 +1109,6 @@ static int si4713_write_econtrol_tune(struct si4713_device *sdev, sdev->antenna_capacitor = antcap; } -unlock: - mutex_unlock(&sdev->mutex); -exit: return rval; } @@ -1159,12 +1125,12 @@ static int si4713_write_econtrol_integers(struct si4713_device *sdev, rval = validate_range(&sdev->sd, control); if (rval < 0) - goto exit; + return rval; rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit, &mask, &property, &mul, &table, &size); if (rval < 0) - goto exit; + return rval; val = control->value; if (mul) { @@ -1172,24 +1138,22 @@ static int si4713_write_econtrol_integers(struct si4713_device *sdev, } else if (table) { rval = usecs_to_dev(control->value, table, size); if (rval < 0) - goto exit; + return rval; val = rval; rval = 0; } - mutex_lock(&sdev->mutex); - if (sdev->power_state) { if (mask) { rval = si4713_read_property(sdev, property, &val); if (rval < 0) - goto unlock; + return rval; val = set_bits(val, control->value, bit, mask); } rval = si4713_write_property(sdev, property, val); if (rval < 0) - goto unlock; + return rval; if (mask) val = control->value; } @@ -1199,16 +1163,13 @@ static int si4713_write_econtrol_integers(struct si4713_device *sdev, } else if (table) { rval = dev_to_usecs(val, table, size); if (rval < 0) - goto unlock; + return rval; *shadow = rval; rval = 0; } else { *shadow = val; } -unlock: - mutex_unlock(&sdev->mutex); -exit: return rval; } @@ -1231,9 +1192,7 @@ static int si4713_setup(struct si4713_device *sdev) return -ENOMEM; /* Get a local copy to avoid race */ - mutex_lock(&sdev->mutex); memcpy(tmp, sdev, sizeof(*sdev)); - mutex_unlock(&sdev->mutex); ctrl.id = V4L2_CID_RDS_TX_PI; ctrl.value = tmp->rds_info.pi; @@ -1338,17 +1297,15 @@ static int si4713_initialize(struct si4713_device *sdev) rval = si4713_set_power_state(sdev, POWER_ON); if (rval < 0) - goto exit; + return rval; rval = si4713_checkrev(sdev); if (rval < 0) - goto exit; + return rval; rval = si4713_set_power_state(sdev, POWER_OFF); if (rval < 0) - goto exit; - - mutex_lock(&sdev->mutex); + return rval; sdev->rds_info.pi = DEFAULT_RDS_PI; sdev->rds_info.pty = DEFAULT_RDS_PTY; @@ -1380,9 +1337,6 @@ static int si4713_initialize(struct si4713_device *sdev) sdev->stereo = 1; sdev->tune_rnl = DEFAULT_TUNE_RNL; - mutex_unlock(&sdev->mutex); - -exit: return rval; } @@ -1428,7 +1382,7 @@ exit: /* * si4713_update_tune_status - update properties from tx_tune_status - * command. Must be called with sdev->mutex held. + * command. * @sdev: si4713_device structure for the device we are communicating */ static int si4713_update_tune_status(struct si4713_device *sdev) @@ -1456,12 +1410,10 @@ static int si4713_read_econtrol_tune(struct si4713_device *sdev, { s32 rval = 0; - mutex_lock(&sdev->mutex); - if (sdev->power_state) { rval = si4713_update_tune_status(sdev); if (rval < 0) - goto unlock; + return rval; } switch (control->id) { @@ -1472,11 +1424,9 @@ static int si4713_read_econtrol_tune(struct si4713_device *sdev, control->value = sdev->antenna_capacitor; break; default: - rval = -EINVAL; + return -EINVAL; } -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1494,14 +1444,12 @@ static int si4713_read_econtrol_integers(struct si4713_device *sdev, rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit, &mask, &property, &mul, &table, &size); if (rval < 0) - goto exit; - - mutex_lock(&sdev->mutex); + return rval; if (sdev->power_state) { rval = si4713_read_property(sdev, property, &val); if (rval < 0) - goto unlock; + return rval; /* Keep negative values for threshold */ if (control->id == V4L2_CID_AUDIO_COMPRESSION_THRESHOLD) @@ -1516,9 +1464,6 @@ static int si4713_read_econtrol_integers(struct si4713_device *sdev, control->value = *shadow; -unlock: - mutex_unlock(&sdev->mutex); -exit: return rval; } @@ -1712,14 +1657,12 @@ static int si4713_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) if (!sdev) return -ENODEV; - mutex_lock(&sdev->mutex); - if (sdev->power_state) { rval = si4713_read_property(sdev, SI4713_TX_LINE_INPUT_MUTE, &sdev->mute); if (rval < 0) - goto unlock; + return rval; } switch (ctrl->id) { @@ -1728,8 +1671,6 @@ static int si4713_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) break; } -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1779,7 +1720,6 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) if (!arg) return -EINVAL; - mutex_lock(&sdev->mutex); switch (cmd) { case SI4713_IOC_MEASURE_RNL: frequency = v4l2_to_si4713(rnl->frequency); @@ -1788,11 +1728,11 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) /* Set desired measurement frequency */ rval = si4713_tx_tune_measure(sdev, frequency, 0); if (rval < 0) - goto unlock; + return rval; /* get results from tune status */ rval = si4713_update_tune_status(sdev); if (rval < 0) - goto unlock; + return rval; } rnl->rnl = sdev->tune_rnl; break; @@ -1802,8 +1742,6 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) rval = -ENOIOCTLCMD; } -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1822,15 +1760,11 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; - if (!sdev) { - rval = -ENODEV; - goto exit; - } + if (!sdev) + return -ENODEV; - if (vm->index > 0) { - rval = -EINVAL; - goto exit; - } + if (vm->index > 0) + return -EINVAL; strncpy(vm->name, "FM Modulator", 32); vm->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW | @@ -1840,15 +1774,13 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) vm->rangelow = si4713_to_v4l2(FREQ_RANGE_LOW); vm->rangehigh = si4713_to_v4l2(FREQ_RANGE_HIGH); - mutex_lock(&sdev->mutex); - if (sdev->power_state) { u32 comp_en = 0; rval = si4713_read_property(sdev, SI4713_TX_COMPONENT_ENABLE, &comp_en); if (rval < 0) - goto unlock; + return rval; sdev->stereo = get_status_bit(comp_en, 1, 1 << 1); sdev->rds_info.enabled = get_status_bit(comp_en, 2, 1 << 2); @@ -1866,9 +1798,6 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) else vm->txsubchans &= ~V4L2_TUNER_SUB_RDS; -unlock: - mutex_unlock(&sdev->mutex); -exit: return rval; } @@ -1896,13 +1825,11 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato rds = !!(vm->txsubchans & V4L2_TUNER_SUB_RDS); - mutex_lock(&sdev->mutex); - if (sdev->power_state) { rval = si4713_read_property(sdev, SI4713_TX_COMPONENT_ENABLE, &p); if (rval < 0) - goto unlock; + return rval; p = set_bits(p, stereo, 1, 1 << 1); p = set_bits(p, rds, 2, 1 << 2); @@ -1910,14 +1837,12 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato rval = si4713_write_property(sdev, SI4713_TX_COMPONENT_ENABLE, p); if (rval < 0) - goto unlock; + return rval; } sdev->stereo = stereo; sdev->rds_info.enabled = rds; -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1929,23 +1854,19 @@ static int si4713_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) f->type = V4L2_TUNER_RADIO; - mutex_lock(&sdev->mutex); - if (sdev->power_state) { u16 freq; u8 p, a, n; rval = si4713_tx_tune_status(sdev, 0x00, &freq, &p, &a, &n); if (rval < 0) - goto unlock; + return rval; sdev->frequency = freq; } f->frequency = si4713_to_v4l2(sdev->frequency); -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1960,19 +1881,15 @@ static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequenc if (frequency < FREQ_RANGE_LOW || frequency > FREQ_RANGE_HIGH) return -EDOM; - mutex_lock(&sdev->mutex); - if (sdev->power_state) { rval = si4713_tx_tune_freq(sdev, frequency); if (rval < 0) - goto unlock; + return rval; frequency = rval; rval = 0; } sdev->frequency = frequency; -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -2030,7 +1947,6 @@ static int si4713_probe(struct i2c_client *client, v4l2_i2c_subdev_init(&sdev->sd, client, &si4713_subdev_ops); - mutex_init(&sdev->mutex); init_completion(&sdev->work); if (client->irq) { diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713-i2c.h index c6dfa7f..979828d 100644 --- a/drivers/media/radio/si4713-i2c.h +++ b/drivers/media/radio/si4713-i2c.h @@ -220,7 +220,6 @@ struct si4713_device { /* v4l2_subdev and i2c reference (v4l2_subdev priv data) */ struct v4l2_subdev sd; /* private data structures */ - struct mutex mutex; struct completion work; struct rds_info rds_info; struct limiter_info limiter_info; -- cgit v1.1 From b387754d7256b1fa3610ed8a85f9274df49d1178 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 8 Apr 2013 17:25:05 -0300 Subject: [media] radio-si4713: fix g/s_frequency - check for invalid modulators. - clamp frequency to valid range. Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/si4713-i2c.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c index 1cb9a2e..56c9241 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713-i2c.c @@ -1261,6 +1261,7 @@ static int si4713_setup(struct si4713_device *sdev) rval |= si4713_set_rds_radio_text(sdev, tmp->rds_info.radio_text); /* Device procedure needs to set frequency first */ + f.tuner = 0; f.frequency = tmp->frequency ? tmp->frequency : DEFAULT_FREQUENCY; f.frequency = si4713_to_v4l2(f.frequency); rval |= si4713_s_frequency(&sdev->sd, &f); @@ -1852,7 +1853,8 @@ static int si4713_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; - f->type = V4L2_TUNER_RADIO; + if (f->tuner) + return -EINVAL; if (sdev->power_state) { u16 freq; @@ -1877,9 +1879,11 @@ static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequenc int rval = 0; u16 frequency = v4l2_to_si4713(f->frequency); + if (f->tuner) + return -EINVAL; + /* Check frequency range */ - if (frequency < FREQ_RANGE_LOW || frequency > FREQ_RANGE_HIGH) - return -EDOM; + frequency = clamp_t(u16, frequency, FREQ_RANGE_LOW, FREQ_RANGE_HIGH); if (sdev->power_state) { rval = si4713_tx_tune_freq(sdev, frequency); -- cgit v1.1 From 03aa1bcd92eca82735e1de6d3e71bebf6e1c516e Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 8 Apr 2013 17:27:18 -0300 Subject: [media] radio-si4713: convert to the control framework Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 89 +--- drivers/media/radio/si4713-i2c.c | 908 +++++++++---------------------------- drivers/media/radio/si4713-i2c.h | 65 ++- 3 files changed, 239 insertions(+), 823 deletions(-) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index 633c545..f8c6137 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -76,61 +76,6 @@ static int radio_si4713_querycap(struct file *file, void *priv, return 0; } -/* radio_si4713_queryctrl - enumerate control items */ -static int radio_si4713_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - /* Must be sorted from low to high control ID! */ - static const u32 user_ctrls[] = { - V4L2_CID_USER_CLASS, - V4L2_CID_AUDIO_MUTE, - 0 - }; - - /* Must be sorted from low to high control ID! */ - static const u32 fmtx_ctrls[] = { - V4L2_CID_FM_TX_CLASS, - V4L2_CID_RDS_TX_DEVIATION, - V4L2_CID_RDS_TX_PI, - V4L2_CID_RDS_TX_PTY, - V4L2_CID_RDS_TX_PS_NAME, - V4L2_CID_RDS_TX_RADIO_TEXT, - V4L2_CID_AUDIO_LIMITER_ENABLED, - V4L2_CID_AUDIO_LIMITER_RELEASE_TIME, - V4L2_CID_AUDIO_LIMITER_DEVIATION, - V4L2_CID_AUDIO_COMPRESSION_ENABLED, - V4L2_CID_AUDIO_COMPRESSION_GAIN, - V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, - V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, - V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME, - V4L2_CID_PILOT_TONE_ENABLED, - V4L2_CID_PILOT_TONE_DEVIATION, - V4L2_CID_PILOT_TONE_FREQUENCY, - V4L2_CID_TUNE_PREEMPHASIS, - V4L2_CID_TUNE_POWER_LEVEL, - V4L2_CID_TUNE_ANTENNA_CAPACITOR, - 0 - }; - static const u32 *ctrl_classes[] = { - user_ctrls, - fmtx_ctrls, - NULL - }; - struct radio_si4713_device *rsdev; - - rsdev = video_get_drvdata(video_devdata(file)); - - qc->id = v4l2_ctrl_next(ctrl_classes, qc->id); - if (qc->id == 0) - return -EINVAL; - - if (qc->id == V4L2_CID_USER_CLASS || qc->id == V4L2_CID_FM_TX_CLASS) - return v4l2_ctrl_query_fill(qc, 0, 0, 0, 0); - - return v4l2_device_call_until_err(&rsdev->v4l2_dev, 0, core, - queryctrl, qc); -} - /* * v4l2 ioctl call backs. * we are just a wrapper for v4l2_sub_devs. @@ -140,34 +85,6 @@ static inline struct v4l2_device *get_v4l2_dev(struct file *file) return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev; } -static int radio_si4713_g_ext_ctrls(struct file *file, void *p, - struct v4l2_ext_controls *vecs) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - g_ext_ctrls, vecs); -} - -static int radio_si4713_s_ext_ctrls(struct file *file, void *p, - struct v4l2_ext_controls *vecs) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - s_ext_ctrls, vecs); -} - -static int radio_si4713_g_ctrl(struct file *file, void *p, - struct v4l2_control *vc) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - g_ctrl, vc); -} - -static int radio_si4713_s_ctrl(struct file *file, void *p, - struct v4l2_control *vc) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - s_ctrl, vc); -} - static int radio_si4713_g_modulator(struct file *file, void *p, struct v4l2_modulator *vm) { @@ -205,11 +122,6 @@ static long radio_si4713_default(struct file *file, void *p, static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { .vidioc_querycap = radio_si4713_querycap, - .vidioc_queryctrl = radio_si4713_queryctrl, - .vidioc_g_ext_ctrls = radio_si4713_g_ext_ctrls, - .vidioc_s_ext_ctrls = radio_si4713_s_ext_ctrls, - .vidioc_g_ctrl = radio_si4713_g_ctrl, - .vidioc_s_ctrl = radio_si4713_s_ctrl, .vidioc_g_modulator = radio_si4713_g_modulator, .vidioc_s_modulator = radio_si4713_s_modulator, .vidioc_g_frequency = radio_si4713_g_frequency, @@ -274,6 +186,7 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) rsdev->radio_dev = radio_si4713_vdev_template; rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev; + rsdev->radio_dev.ctrl_handler = sd->ctrl_handler; /* Serialize all access to the si4713 */ rsdev->radio_dev.lock = &rsdev->lock; video_set_drvdata(&rsdev->radio_dev, rsdev); diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c index 56c9241..fe16088 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713-i2c.c @@ -52,8 +52,6 @@ static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = { #define DEFAULT_RDS_PI 0x00 #define DEFAULT_RDS_PTY 0x00 -#define DEFAULT_RDS_PS_NAME "" -#define DEFAULT_RDS_RADIO_TEXT DEFAULT_RDS_PS_NAME #define DEFAULT_RDS_DEVIATION 0x00C8 #define DEFAULT_RDS_PS_REPEAT_COUNT 0x0003 #define DEFAULT_LIMITER_RTIME 0x1392 @@ -107,7 +105,6 @@ static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = { (status & SI4713_ERR)) /* mute definition */ #define set_mute(p) ((p & 1) | ((p & 1) << 1)); -#define get_mute(p) (p & 0x01) #ifdef DEBUG #define DBG_BUFFER(device, message, buffer, size) \ @@ -189,21 +186,6 @@ static int usecs_to_dev(unsigned long usecs, unsigned long const array[], return rval; } -static unsigned long dev_to_usecs(int value, unsigned long const array[], - int size) -{ - int i; - int rval = -EINVAL; - - for (i = 0; i < size / 2; i++) - if (array[i * 2] == value) { - rval = array[(i * 2) + 1]; - break; - } - - return rval; -} - /* si4713_handler: IRQ handler, just complete work */ static irqreturn_t si4713_handler(int irq, void *dev) { @@ -787,9 +769,6 @@ static int si4713_set_mute(struct si4713_device *sdev, u16 mute) rval = si4713_write_property(sdev, SI4713_TX_LINE_INPUT_MUTE, mute); - if (rval >= 0) - sdev->mute = get_mute(mute); - return rval; } @@ -830,7 +809,6 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name) return rval; } - strncpy(sdev->rds_info.ps_name, ps_name, MAX_RDS_PS_NAME); return rval; } @@ -842,24 +820,23 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) s8 left; if (!sdev->power_state) - goto copy; + return rval; rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_CLEAR, 0, 0, 0, &left); if (rval < 0) return rval; if (!strlen(rt)) - goto copy; + return rval; do { /* RDS spec says that if the last block isn't used, * then apply a carriage return */ - if (t_index < (RDS_RADIOTEXT_INDEX_MAX * - RDS_RADIOTEXT_BLK_SIZE)) { + if (t_index < (RDS_RADIOTEXT_INDEX_MAX * RDS_RADIOTEXT_BLK_SIZE)) { for (i = 0; i < RDS_RADIOTEXT_BLK_SIZE; i++) { - if (!rt[t_index + i] || rt[t_index + i] == - RDS_CARRIAGE_RETURN) { + if (!rt[t_index + i] || + rt[t_index + i] == RDS_CARRIAGE_RETURN) { rt[t_index + i] = RDS_CARRIAGE_RETURN; cr_inserted = 1; break; @@ -881,13 +858,38 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) break; } while (left > 0); -copy: - strncpy(sdev->rds_info.radio_text, rt, MAX_RDS_RADIO_TEXT); + return rval; +} + +/* + * si4713_update_tune_status - update properties from tx_tune_status + * command. Must be called with sdev->mutex held. + * @sdev: si4713_device structure for the device we are communicating + */ +static int si4713_update_tune_status(struct si4713_device *sdev) +{ + int rval; + u16 f = 0; + u8 p = 0, a = 0, n = 0; + + rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n); + + if (rval < 0) + goto exit; + +/* TODO: check that power_level and antenna_capacitor really are not + changed by the hardware. If they are, then these controls should become + volatiles. + sdev->power_level = p; + sdev->antenna_capacitor = a;*/ + sdev->tune_rnl = n; + +exit: return rval; } static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id, - u32 **shadow, s32 *bit, s32 *mask, u16 *property, int *mul, + s32 *bit, s32 *mask, u16 *property, int *mul, unsigned long **table, int *size) { s32 rval = 0; @@ -897,277 +899,76 @@ static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id, case V4L2_CID_RDS_TX_PI: *property = SI4713_TX_RDS_PI; *mul = 1; - *shadow = &sdev->rds_info.pi; break; case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: *property = SI4713_TX_ACOMP_THRESHOLD; *mul = 1; - *shadow = &sdev->acomp_info.threshold; break; case V4L2_CID_AUDIO_COMPRESSION_GAIN: *property = SI4713_TX_ACOMP_GAIN; *mul = 1; - *shadow = &sdev->acomp_info.gain; break; case V4L2_CID_PILOT_TONE_FREQUENCY: *property = SI4713_TX_PILOT_FREQUENCY; *mul = 1; - *shadow = &sdev->pilot_info.frequency; break; case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: *property = SI4713_TX_ACOMP_ATTACK_TIME; *mul = ATTACK_TIME_UNIT; - *shadow = &sdev->acomp_info.attack_time; break; case V4L2_CID_PILOT_TONE_DEVIATION: *property = SI4713_TX_PILOT_DEVIATION; *mul = 10; - *shadow = &sdev->pilot_info.deviation; break; case V4L2_CID_AUDIO_LIMITER_DEVIATION: *property = SI4713_TX_AUDIO_DEVIATION; *mul = 10; - *shadow = &sdev->limiter_info.deviation; break; case V4L2_CID_RDS_TX_DEVIATION: *property = SI4713_TX_RDS_DEVIATION; *mul = 1; - *shadow = &sdev->rds_info.deviation; break; case V4L2_CID_RDS_TX_PTY: *property = SI4713_TX_RDS_PS_MISC; *bit = 5; *mask = 0x1F << 5; - *shadow = &sdev->rds_info.pty; break; case V4L2_CID_AUDIO_LIMITER_ENABLED: *property = SI4713_TX_ACOMP_ENABLE; *bit = 1; *mask = 1 << 1; - *shadow = &sdev->limiter_info.enabled; break; case V4L2_CID_AUDIO_COMPRESSION_ENABLED: *property = SI4713_TX_ACOMP_ENABLE; *bit = 0; *mask = 1 << 0; - *shadow = &sdev->acomp_info.enabled; break; case V4L2_CID_PILOT_TONE_ENABLED: *property = SI4713_TX_COMPONENT_ENABLE; *bit = 0; *mask = 1 << 0; - *shadow = &sdev->pilot_info.enabled; break; case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: *property = SI4713_TX_LIMITER_RELEASE_TIME; *table = limiter_times; *size = ARRAY_SIZE(limiter_times); - *shadow = &sdev->limiter_info.release_time; break; case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: *property = SI4713_TX_ACOMP_RELEASE_TIME; *table = acomp_rtimes; *size = ARRAY_SIZE(acomp_rtimes); - *shadow = &sdev->acomp_info.release_time; break; case V4L2_CID_TUNE_PREEMPHASIS: *property = SI4713_TX_PREEMPHASIS; *table = preemphasis_values; *size = ARRAY_SIZE(preemphasis_values); - *shadow = &sdev->preemphasis; break; default: rval = -EINVAL; - } - - return rval; -} - -static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc); - -/* write string property */ -static int si4713_write_econtrol_string(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - struct v4l2_queryctrl vqc; - int len; - s32 rval = 0; - - vqc.id = control->id; - rval = si4713_queryctrl(&sdev->sd, &vqc); - if (rval < 0) - goto exit; - - switch (control->id) { - case V4L2_CID_RDS_TX_PS_NAME: { - char ps_name[MAX_RDS_PS_NAME + 1]; - - len = control->size - 1; - if (len < 0 || len > MAX_RDS_PS_NAME) { - rval = -ERANGE; - goto exit; - } - rval = copy_from_user(ps_name, control->string, len); - if (rval) { - rval = -EFAULT; - goto exit; - } - ps_name[len] = '\0'; - - if (strlen(ps_name) % vqc.step) { - rval = -ERANGE; - goto exit; - } - - rval = si4713_set_rds_ps_name(sdev, ps_name); - } - break; - - case V4L2_CID_RDS_TX_RADIO_TEXT: { - char radio_text[MAX_RDS_RADIO_TEXT + 1]; - - len = control->size - 1; - if (len < 0 || len > MAX_RDS_RADIO_TEXT) { - rval = -ERANGE; - goto exit; - } - rval = copy_from_user(radio_text, control->string, len); - if (rval) { - rval = -EFAULT; - goto exit; - } - radio_text[len] = '\0'; - - if (strlen(radio_text) % vqc.step) { - rval = -ERANGE; - goto exit; - } - - rval = si4713_set_rds_radio_text(sdev, radio_text); - } - break; - - default: - rval = -EINVAL; - break; - } - -exit: - return rval; -} - -static int validate_range(struct v4l2_subdev *sd, - struct v4l2_ext_control *control) -{ - struct v4l2_queryctrl vqc; - int rval; - - vqc.id = control->id; - rval = si4713_queryctrl(sd, &vqc); - if (rval < 0) - goto exit; - - if (control->value < vqc.minimum || control->value > vqc.maximum) - rval = -ERANGE; - -exit: - return rval; -} - -/* properties which use tx_tune_power*/ -static int si4713_write_econtrol_tune(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval = 0; - u8 power, antcap; - - rval = validate_range(&sdev->sd, control); - if (rval < 0) - return rval; - - switch (control->id) { - case V4L2_CID_TUNE_POWER_LEVEL: - power = control->value; - antcap = sdev->antenna_capacitor; - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - power = sdev->power_level; - antcap = control->value; break; - default: - return -EINVAL; - } - - if (sdev->power_state) - rval = si4713_tx_tune_power(sdev, power, antcap); - - if (rval == 0) { - sdev->power_level = power; - sdev->antenna_capacitor = antcap; - } - - return rval; -} - -static int si4713_write_econtrol_integers(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval; - u32 *shadow = NULL, val = 0; - s32 bit = 0, mask = 0; - u16 property = 0; - int mul = 0; - unsigned long *table = NULL; - int size = 0; - - rval = validate_range(&sdev->sd, control); - if (rval < 0) - return rval; - - rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit, - &mask, &property, &mul, &table, &size); - if (rval < 0) - return rval; - - val = control->value; - if (mul) { - val = control->value / mul; - } else if (table) { - rval = usecs_to_dev(control->value, table, size); - if (rval < 0) - return rval; - val = rval; - rval = 0; - } - - if (sdev->power_state) { - if (mask) { - rval = si4713_read_property(sdev, property, &val); - if (rval < 0) - return rval; - val = set_bits(val, control->value, bit, mask); - } - - rval = si4713_write_property(sdev, property, val); - if (rval < 0) - return rval; - if (mask) - val = control->value; - } - - if (mul) { - *shadow = val * mul; - } else if (table) { - rval = dev_to_usecs(val, table, size); - if (rval < 0) - return rval; - *shadow = rval; - rval = 0; - } else { - *shadow = val; } return rval; @@ -1181,110 +982,25 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato */ static int si4713_setup(struct si4713_device *sdev) { - struct v4l2_ext_control ctrl; struct v4l2_frequency f; struct v4l2_modulator vm; - struct si4713_device *tmp; - int rval = 0; - - tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); - if (!tmp) - return -ENOMEM; - - /* Get a local copy to avoid race */ - memcpy(tmp, sdev, sizeof(*sdev)); - - ctrl.id = V4L2_CID_RDS_TX_PI; - ctrl.value = tmp->rds_info.pi; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_THRESHOLD; - ctrl.value = tmp->acomp_info.threshold; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_GAIN; - ctrl.value = tmp->acomp_info.gain; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_PILOT_TONE_FREQUENCY; - ctrl.value = tmp->pilot_info.frequency; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME; - ctrl.value = tmp->acomp_info.attack_time; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_PILOT_TONE_DEVIATION; - ctrl.value = tmp->pilot_info.deviation; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_LIMITER_DEVIATION; - ctrl.value = tmp->limiter_info.deviation; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_RDS_TX_DEVIATION; - ctrl.value = tmp->rds_info.deviation; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_RDS_TX_PTY; - ctrl.value = tmp->rds_info.pty; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_LIMITER_ENABLED; - ctrl.value = tmp->limiter_info.enabled; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ENABLED; - ctrl.value = tmp->acomp_info.enabled; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_PILOT_TONE_ENABLED; - ctrl.value = tmp->pilot_info.enabled; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_LIMITER_RELEASE_TIME; - ctrl.value = tmp->limiter_info.release_time; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME; - ctrl.value = tmp->acomp_info.release_time; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_TUNE_PREEMPHASIS; - ctrl.value = tmp->preemphasis; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_RDS_TX_PS_NAME; - rval |= si4713_set_rds_ps_name(sdev, tmp->rds_info.ps_name); - - ctrl.id = V4L2_CID_RDS_TX_RADIO_TEXT; - rval |= si4713_set_rds_radio_text(sdev, tmp->rds_info.radio_text); + int rval; /* Device procedure needs to set frequency first */ f.tuner = 0; - f.frequency = tmp->frequency ? tmp->frequency : DEFAULT_FREQUENCY; + f.frequency = sdev->frequency ? sdev->frequency : DEFAULT_FREQUENCY; f.frequency = si4713_to_v4l2(f.frequency); - rval |= si4713_s_frequency(&sdev->sd, &f); - - ctrl.id = V4L2_CID_TUNE_POWER_LEVEL; - ctrl.value = tmp->power_level; - rval |= si4713_write_econtrol_tune(sdev, &ctrl); - - ctrl.id = V4L2_CID_TUNE_ANTENNA_CAPACITOR; - ctrl.value = tmp->antenna_capacitor; - rval |= si4713_write_econtrol_tune(sdev, &ctrl); + rval = si4713_s_frequency(&sdev->sd, &f); vm.index = 0; - if (tmp->stereo) + if (sdev->stereo) vm.txsubchans = V4L2_TUNER_SUB_STEREO; else vm.txsubchans = V4L2_TUNER_SUB_MONO; - if (tmp->rds_info.enabled) + if (sdev->rds_enabled) vm.txsubchans |= V4L2_TUNER_SUB_RDS; si4713_s_modulator(&sdev->sd, &vm); - kfree(tmp); - return rval; } @@ -1308,406 +1024,116 @@ static int si4713_initialize(struct si4713_device *sdev) if (rval < 0) return rval; - sdev->rds_info.pi = DEFAULT_RDS_PI; - sdev->rds_info.pty = DEFAULT_RDS_PTY; - sdev->rds_info.deviation = DEFAULT_RDS_DEVIATION; - strlcpy(sdev->rds_info.ps_name, DEFAULT_RDS_PS_NAME, MAX_RDS_PS_NAME); - strlcpy(sdev->rds_info.radio_text, DEFAULT_RDS_RADIO_TEXT, - MAX_RDS_RADIO_TEXT); - sdev->rds_info.enabled = 1; - - sdev->limiter_info.release_time = DEFAULT_LIMITER_RTIME; - sdev->limiter_info.deviation = DEFAULT_LIMITER_DEV; - sdev->limiter_info.enabled = 1; - - sdev->pilot_info.deviation = DEFAULT_PILOT_DEVIATION; - sdev->pilot_info.frequency = DEFAULT_PILOT_FREQUENCY; - sdev->pilot_info.enabled = 1; - - sdev->acomp_info.release_time = DEFAULT_ACOMP_RTIME; - sdev->acomp_info.attack_time = DEFAULT_ACOMP_ATIME; - sdev->acomp_info.threshold = DEFAULT_ACOMP_THRESHOLD; - sdev->acomp_info.gain = DEFAULT_ACOMP_GAIN; - sdev->acomp_info.enabled = 1; sdev->frequency = DEFAULT_FREQUENCY; - sdev->preemphasis = DEFAULT_PREEMPHASIS; - sdev->mute = DEFAULT_MUTE; - sdev->power_level = DEFAULT_POWER_LEVEL; - sdev->antenna_capacitor = 0; sdev->stereo = 1; sdev->tune_rnl = DEFAULT_TUNE_RNL; - - return rval; -} - -/* read string property */ -static int si4713_read_econtrol_string(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval = 0; - - switch (control->id) { - case V4L2_CID_RDS_TX_PS_NAME: - if (strlen(sdev->rds_info.ps_name) + 1 > control->size) { - control->size = MAX_RDS_PS_NAME + 1; - rval = -ENOSPC; - goto exit; - } - rval = copy_to_user(control->string, sdev->rds_info.ps_name, - strlen(sdev->rds_info.ps_name) + 1); - if (rval) - rval = -EFAULT; - break; - - case V4L2_CID_RDS_TX_RADIO_TEXT: - if (strlen(sdev->rds_info.radio_text) + 1 > control->size) { - control->size = MAX_RDS_RADIO_TEXT + 1; - rval = -ENOSPC; - goto exit; - } - rval = copy_to_user(control->string, sdev->rds_info.radio_text, - strlen(sdev->rds_info.radio_text) + 1); - if (rval) - rval = -EFAULT; - break; - - default: - rval = -EINVAL; - break; - } - -exit: - return rval; -} - -/* - * si4713_update_tune_status - update properties from tx_tune_status - * command. - * @sdev: si4713_device structure for the device we are communicating - */ -static int si4713_update_tune_status(struct si4713_device *sdev) -{ - int rval; - u16 f = 0; - u8 p = 0, a = 0, n = 0; - - rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n); - - if (rval < 0) - goto exit; - - sdev->power_level = p; - sdev->antenna_capacitor = a; - sdev->tune_rnl = n; - -exit: - return rval; -} - -/* properties which use tx_tune_status */ -static int si4713_read_econtrol_tune(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval = 0; - - if (sdev->power_state) { - rval = si4713_update_tune_status(sdev); - if (rval < 0) - return rval; - } - - switch (control->id) { - case V4L2_CID_TUNE_POWER_LEVEL: - control->value = sdev->power_level; - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - control->value = sdev->antenna_capacitor; - break; - default: - return -EINVAL; - } - - return rval; + return 0; } -static int si4713_read_econtrol_integers(struct si4713_device *sdev, - struct v4l2_ext_control *control) +/* si4713_s_ctrl - set the value of a control */ +static int si4713_s_ctrl(struct v4l2_ctrl *ctrl) { - s32 rval; - u32 *shadow = NULL, val = 0; + struct si4713_device *sdev = + container_of(ctrl->handler, struct si4713_device, ctrl_handler); + u32 val = 0; s32 bit = 0, mask = 0; u16 property = 0; int mul = 0; unsigned long *table = NULL; int size = 0; + bool force = false; + int c; + int ret = 0; - rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit, - &mask, &property, &mul, &table, &size); - if (rval < 0) - return rval; - - if (sdev->power_state) { - rval = si4713_read_property(sdev, property, &val); - if (rval < 0) - return rval; - - /* Keep negative values for threshold */ - if (control->id == V4L2_CID_AUDIO_COMPRESSION_THRESHOLD) - *shadow = (s16)val; - else if (mask) - *shadow = get_status_bit(val, bit, mask); - else if (mul) - *shadow = val * mul; - else - *shadow = dev_to_usecs(val, table, size); - } - - control->value = *shadow; - - return rval; -} - -/* - * Video4Linux Subdev Interface - */ -/* si4713_s_ext_ctrls - set extended controls value */ -static int si4713_s_ext_ctrls(struct v4l2_subdev *sd, - struct v4l2_ext_controls *ctrls) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int i; - - if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) + if (ctrl->id != V4L2_CID_AUDIO_MUTE) return -EINVAL; - - for (i = 0; i < ctrls->count; i++) { - int err; - - switch ((ctrls->controls + i)->id) { - case V4L2_CID_RDS_TX_PS_NAME: - case V4L2_CID_RDS_TX_RADIO_TEXT: - err = si4713_write_econtrol_string(sdev, - ctrls->controls + i); - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - case V4L2_CID_TUNE_POWER_LEVEL: - err = si4713_write_econtrol_tune(sdev, - ctrls->controls + i); - break; - default: - err = si4713_write_econtrol_integers(sdev, - ctrls->controls + i); - } - - if (err < 0) { - ctrls->error_idx = i; - return err; + if (ctrl->is_new) { + if (ctrl->val) { + ret = si4713_set_mute(sdev, ctrl->val); + if (!ret) + ret = si4713_set_power_state(sdev, POWER_DOWN); + return ret; } + ret = si4713_set_power_state(sdev, POWER_UP); + if (!ret) + ret = si4713_set_mute(sdev, ctrl->val); + if (!ret) + ret = si4713_setup(sdev); + if (ret) + return ret; + force = true; } - return 0; -} - -/* si4713_g_ext_ctrls - get extended controls value */ -static int si4713_g_ext_ctrls(struct v4l2_subdev *sd, - struct v4l2_ext_controls *ctrls) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int i; + if (!sdev->power_state) + return 0; - if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) - return -EINVAL; + for (c = 1; !ret && c < ctrl->ncontrols; c++) { + ctrl = ctrl->cluster[c]; - for (i = 0; i < ctrls->count; i++) { - int err; + if (!force && !ctrl->is_new) + continue; - switch ((ctrls->controls + i)->id) { + switch (ctrl->id) { case V4L2_CID_RDS_TX_PS_NAME: + ret = si4713_set_rds_ps_name(sdev, ctrl->string); + break; + case V4L2_CID_RDS_TX_RADIO_TEXT: - err = si4713_read_econtrol_string(sdev, - ctrls->controls + i); + ret = si4713_set_rds_radio_text(sdev, ctrl->string); break; + case V4L2_CID_TUNE_ANTENNA_CAPACITOR: + /* don't handle this control if we force setting all + * controls since in that case it will be handled by + * V4L2_CID_TUNE_POWER_LEVEL. */ + if (force) + break; + /* fall through */ case V4L2_CID_TUNE_POWER_LEVEL: - err = si4713_read_econtrol_tune(sdev, - ctrls->controls + i); + ret = si4713_tx_tune_power(sdev, + sdev->tune_pwr_level->val, sdev->tune_ant_cap->val); + if (!ret) { + /* Make sure we don't set this twice */ + sdev->tune_ant_cap->is_new = false; + sdev->tune_pwr_level->is_new = false; + } break; - default: - err = si4713_read_econtrol_integers(sdev, - ctrls->controls + i); - } - - if (err < 0) { - ctrls->error_idx = i; - return err; - } - } - - return 0; -} - -/* si4713_queryctrl - enumerate control items */ -static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - int rval = 0; - - switch (qc->id) { - /* User class controls */ - case V4L2_CID_AUDIO_MUTE: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, DEFAULT_MUTE); - break; - /* FM_TX class controls */ - case V4L2_CID_RDS_TX_PI: - rval = v4l2_ctrl_query_fill(qc, 0, 0xFFFF, 1, DEFAULT_RDS_PI); - break; - case V4L2_CID_RDS_TX_PTY: - rval = v4l2_ctrl_query_fill(qc, 0, 31, 1, DEFAULT_RDS_PTY); - break; - case V4L2_CID_RDS_TX_DEVIATION: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_DEVIATION, - 10, DEFAULT_RDS_DEVIATION); - break; - case V4L2_CID_RDS_TX_PS_NAME: - /* - * Report step as 8. From RDS spec, psname - * should be 8. But there are receivers which scroll strings - * sized as 8xN. - */ - rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_PS_NAME, 8, 0); - break; - case V4L2_CID_RDS_TX_RADIO_TEXT: - /* - * Report step as 32 (2A block). From RDS spec, - * radio text should be 32 for 2A block. But there are receivers - * which scroll strings sized as 32xN. Setting default to 32. - */ - rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_RADIO_TEXT, 32, 0); - break; - case V4L2_CID_AUDIO_LIMITER_ENABLED: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - break; - case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: - rval = v4l2_ctrl_query_fill(qc, 250, MAX_LIMITER_RELEASE_TIME, - 50, DEFAULT_LIMITER_RTIME); - break; - case V4L2_CID_AUDIO_LIMITER_DEVIATION: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_LIMITER_DEVIATION, - 10, DEFAULT_LIMITER_DEV); - break; - - case V4L2_CID_AUDIO_COMPRESSION_ENABLED: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - break; - case V4L2_CID_AUDIO_COMPRESSION_GAIN: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_GAIN, 1, - DEFAULT_ACOMP_GAIN); - break; - case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: - rval = v4l2_ctrl_query_fill(qc, MIN_ACOMP_THRESHOLD, - MAX_ACOMP_THRESHOLD, 1, - DEFAULT_ACOMP_THRESHOLD); - break; - case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_ATTACK_TIME, - 500, DEFAULT_ACOMP_ATIME); - break; - case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: - rval = v4l2_ctrl_query_fill(qc, 100000, MAX_ACOMP_RELEASE_TIME, - 100000, DEFAULT_ACOMP_RTIME); - break; - - case V4L2_CID_PILOT_TONE_ENABLED: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - break; - case V4L2_CID_PILOT_TONE_DEVIATION: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_DEVIATION, - 10, DEFAULT_PILOT_DEVIATION); - break; - case V4L2_CID_PILOT_TONE_FREQUENCY: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_FREQUENCY, - 1, DEFAULT_PILOT_FREQUENCY); - break; - - case V4L2_CID_TUNE_PREEMPHASIS: - rval = v4l2_ctrl_query_fill(qc, V4L2_PREEMPHASIS_DISABLED, - V4L2_PREEMPHASIS_75_uS, 1, - V4L2_PREEMPHASIS_50_uS); - break; - case V4L2_CID_TUNE_POWER_LEVEL: - rval = v4l2_ctrl_query_fill(qc, 0, 120, 1, DEFAULT_POWER_LEVEL); - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - rval = v4l2_ctrl_query_fill(qc, 0, 191, 1, 0); - break; - default: - rval = -EINVAL; - break; - } - - return rval; -} - -/* si4713_g_ctrl - get the value of a control */ -static int si4713_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int rval = 0; - - if (!sdev) - return -ENODEV; - - if (sdev->power_state) { - rval = si4713_read_property(sdev, SI4713_TX_LINE_INPUT_MUTE, - &sdev->mute); - - if (rval < 0) - return rval; - } - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = get_mute(sdev->mute); - break; - } - - return rval; -} - -/* si4713_s_ctrl - set the value of a control */ -static int si4713_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int rval = 0; - - if (!sdev) - return -ENODEV; - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) { - rval = si4713_set_mute(sdev, ctrl->value); - if (rval < 0) - goto exit; - - rval = si4713_set_power_state(sdev, POWER_DOWN); - } else { - rval = si4713_set_power_state(sdev, POWER_UP); - if (rval < 0) - goto exit; + default: + ret = si4713_choose_econtrol_action(sdev, ctrl->id, &bit, + &mask, &property, &mul, &table, &size); + if (ret < 0) + break; + + val = ctrl->val; + if (mul) { + val = val / mul; + } else if (table) { + ret = usecs_to_dev(val, table, size); + if (ret < 0) + break; + val = ret; + ret = 0; + } - rval = si4713_setup(sdev); - if (rval < 0) - goto exit; + if (mask) { + ret = si4713_read_property(sdev, property, &val); + if (ret < 0) + break; + val = set_bits(val, ctrl->val, bit, mask); + } - rval = si4713_set_mute(sdev, ctrl->value); + ret = si4713_write_property(sdev, property, val); + if (ret < 0) + break; + if (mask) + val = ctrl->val; + break; } - break; } -exit: - return rval; + return ret; } /* si4713_ioctl - deal with private ioctls (only rnl for now) */ @@ -1746,15 +1172,6 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) return rval; } -static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = { - .queryctrl = si4713_queryctrl, - .g_ext_ctrls = si4713_g_ext_ctrls, - .s_ext_ctrls = si4713_s_ext_ctrls, - .g_ctrl = si4713_g_ctrl, - .s_ctrl = si4713_s_ctrl, - .ioctl = si4713_ioctl, -}; - /* si4713_g_modulator - get modulator attributes */ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) { @@ -1784,7 +1201,6 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) return rval; sdev->stereo = get_status_bit(comp_en, 1, 1 << 1); - sdev->rds_info.enabled = get_status_bit(comp_en, 2, 1 << 2); } /* Report current audio mode: mono or stereo */ @@ -1794,7 +1210,7 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) vm->txsubchans = V4L2_TUNER_SUB_MONO; /* Report rds feature status */ - if (sdev->rds_info.enabled) + if (sdev->rds_enabled) vm->txsubchans |= V4L2_TUNER_SUB_RDS; else vm->txsubchans &= ~V4L2_TUNER_SUB_RDS; @@ -1842,7 +1258,7 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato } sdev->stereo = stereo; - sdev->rds_info.enabled = rds; + sdev->rds_enabled = rds; return rval; } @@ -1897,6 +1313,14 @@ static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequenc return rval; } +static const struct v4l2_ctrl_ops si4713_ctrl_ops = { + .s_ctrl = si4713_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = { + .ioctl = si4713_ioctl, +}; + static const struct v4l2_subdev_tuner_ops si4713_subdev_tuner_ops = { .g_frequency = si4713_g_frequency, .s_frequency = si4713_s_frequency, @@ -1918,6 +1342,7 @@ static int si4713_probe(struct i2c_client *client, { struct si4713_device *sdev; struct si4713_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; int rval, i; sdev = kzalloc(sizeof *sdev, GFP_KERNEL); @@ -1953,6 +1378,82 @@ static int si4713_probe(struct i2c_client *client, init_completion(&sdev->work); + hdl = &sdev->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 20); + sdev->mute = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, DEFAULT_MUTE); + + sdev->rds_pi = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_PI, 0, 0xffff, 1, DEFAULT_RDS_PI); + sdev->rds_pty = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_PTY, 0, 31, 1, DEFAULT_RDS_PTY); + sdev->rds_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_DEVIATION, 0, MAX_RDS_DEVIATION, + 10, DEFAULT_RDS_DEVIATION); + /* + * Report step as 8. From RDS spec, psname + * should be 8. But there are receivers which scroll strings + * sized as 8xN. + */ + sdev->rds_ps_name = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_PS_NAME, 0, MAX_RDS_PS_NAME, 8, 0); + /* + * Report step as 32 (2A block). From RDS spec, + * radio text should be 32 for 2A block. But there are receivers + * which scroll strings sized as 32xN. Setting default to 32. + */ + sdev->rds_radio_text = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_RADIO_TEXT, 0, MAX_RDS_RADIO_TEXT, 32, 0); + + sdev->limiter_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_LIMITER_ENABLED, 0, 1, 1, 1); + sdev->limiter_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_LIMITER_RELEASE_TIME, 250, + MAX_LIMITER_RELEASE_TIME, 10, DEFAULT_LIMITER_RTIME); + sdev->limiter_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_LIMITER_DEVIATION, 0, + MAX_LIMITER_DEVIATION, 10, DEFAULT_LIMITER_DEV); + + sdev->compression_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_ENABLED, 0, 1, 1, 1); + sdev->compression_gain = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_GAIN, 0, MAX_ACOMP_GAIN, 1, + DEFAULT_ACOMP_GAIN); + sdev->compression_threshold = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, MIN_ACOMP_THRESHOLD, + MAX_ACOMP_THRESHOLD, 1, + DEFAULT_ACOMP_THRESHOLD); + sdev->compression_attack_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, 0, + MAX_ACOMP_ATTACK_TIME, 500, DEFAULT_ACOMP_ATIME); + sdev->compression_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME, 100000, + MAX_ACOMP_RELEASE_TIME, 100000, DEFAULT_ACOMP_RTIME); + + sdev->pilot_tone_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_PILOT_TONE_ENABLED, 0, 1, 1, 1); + sdev->pilot_tone_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_PILOT_TONE_DEVIATION, 0, MAX_PILOT_DEVIATION, + 10, DEFAULT_PILOT_DEVIATION); + sdev->pilot_tone_freq = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_PILOT_TONE_FREQUENCY, 0, MAX_PILOT_FREQUENCY, + 1, DEFAULT_PILOT_FREQUENCY); + + sdev->tune_preemphasis = v4l2_ctrl_new_std_menu(hdl, &si4713_ctrl_ops, + V4L2_CID_TUNE_PREEMPHASIS, + V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS); + sdev->tune_pwr_level = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_TUNE_POWER_LEVEL, 0, 120, 1, DEFAULT_POWER_LEVEL); + sdev->tune_ant_cap = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, 191, 1, 0); + + if (hdl->error) { + rval = hdl->error; + goto free_ctrls; + } + v4l2_ctrl_cluster(20, &sdev->mute); + sdev->sd.ctrl_handler = hdl; + if (client->irq) { rval = request_irq(client->irq, si4713_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, @@ -1977,6 +1478,8 @@ static int si4713_probe(struct i2c_client *client, free_irq: if (client->irq) free_irq(client->irq, sdev); +free_ctrls: + v4l2_ctrl_handler_free(hdl); put_reg: regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies); free_gpio: @@ -2001,6 +1504,7 @@ static int si4713_remove(struct i2c_client *client) free_irq(client->irq, sdev); v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies); if (gpio_is_valid(sdev->gpio_reset)) gpio_free(sdev->gpio_reset); diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713-i2c.h index 979828d..25cdea2 100644 --- a/drivers/media/radio/si4713-i2c.h +++ b/drivers/media/radio/si4713-i2c.h @@ -16,6 +16,7 @@ #define SI4713_I2C_H #include +#include #include #define SI4713_PRODUCT_NUMBER 0x0D @@ -160,56 +161,33 @@ #define POWER_UP 0x01 #define POWER_DOWN 0x00 -struct rds_info { - u32 pi; #define MAX_RDS_PTY 31 - u32 pty; #define MAX_RDS_DEVIATION 90000 - u32 deviation; + /* * PSNAME is known to be defined as 8 character sized (RDS Spec). * However, there is receivers which scroll PSNAME 8xN sized. */ #define MAX_RDS_PS_NAME 96 - u8 ps_name[MAX_RDS_PS_NAME + 1]; + /* * MAX_RDS_RADIO_TEXT is known to be defined as 32 (2A group) or 64 (2B group) * character sized (RDS Spec). * However, there is receivers which scroll them as well. */ #define MAX_RDS_RADIO_TEXT 384 - u8 radio_text[MAX_RDS_RADIO_TEXT + 1]; - u32 enabled; -}; -struct limiter_info { #define MAX_LIMITER_RELEASE_TIME 102390 - u32 release_time; #define MAX_LIMITER_DEVIATION 90000 - u32 deviation; - u32 enabled; -}; -struct pilot_info { #define MAX_PILOT_DEVIATION 90000 - u32 deviation; #define MAX_PILOT_FREQUENCY 19000 - u32 frequency; - u32 enabled; -}; -struct acomp_info { #define MAX_ACOMP_RELEASE_TIME 1000000 - u32 release_time; #define MAX_ACOMP_ATTACK_TIME 5000 - u32 attack_time; #define MAX_ACOMP_THRESHOLD 0 #define MIN_ACOMP_THRESHOLD (-40) - s32 threshold; #define MAX_ACOMP_GAIN 20 - u32 gain; - u32 enabled; -}; #define SI4713_NUM_SUPPLIES 2 @@ -219,20 +197,41 @@ struct acomp_info { struct si4713_device { /* v4l2_subdev and i2c reference (v4l2_subdev priv data) */ struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_handler; /* private data structures */ + struct { /* si4713 control cluster */ + /* This is one big cluster since the mute control + * powers off the device and after unmuting again all + * controls need to be set at once. The only way of doing + * that is by making it one big cluster. */ + struct v4l2_ctrl *mute; + struct v4l2_ctrl *rds_ps_name; + struct v4l2_ctrl *rds_radio_text; + struct v4l2_ctrl *rds_pi; + struct v4l2_ctrl *rds_deviation; + struct v4l2_ctrl *rds_pty; + struct v4l2_ctrl *compression_enabled; + struct v4l2_ctrl *compression_threshold; + struct v4l2_ctrl *compression_gain; + struct v4l2_ctrl *compression_attack_time; + struct v4l2_ctrl *compression_release_time; + struct v4l2_ctrl *pilot_tone_enabled; + struct v4l2_ctrl *pilot_tone_freq; + struct v4l2_ctrl *pilot_tone_deviation; + struct v4l2_ctrl *limiter_enabled; + struct v4l2_ctrl *limiter_deviation; + struct v4l2_ctrl *limiter_release_time; + struct v4l2_ctrl *tune_preemphasis; + struct v4l2_ctrl *tune_pwr_level; + struct v4l2_ctrl *tune_ant_cap; + }; struct completion work; - struct rds_info rds_info; - struct limiter_info limiter_info; - struct pilot_info pilot_info; - struct acomp_info acomp_info; struct regulator_bulk_data supplies[SI4713_NUM_SUPPLIES]; int gpio_reset; + u32 power_state; + u32 rds_enabled; u32 frequency; u32 preemphasis; - u32 mute; - u32 power_level; - u32 power_state; - u32 antenna_capacitor; u32 stereo; u32 tune_rnl; }; -- cgit v1.1 From cd29ed853f90b39318643301d5389e80a7d466bc Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 8 Apr 2013 07:23:32 -0300 Subject: [media] radio-si4713: add prio checking and control events Signed-off-by: Hans Verkuil Acked-by: Eduardo Valentin Tested-by: Eduardo Valentin Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/radio-si4713.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c index f8c6137..ba4cfc9 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/radio-si4713.c @@ -31,6 +31,9 @@ #include #include #include +#include +#include +#include #include /* module parameters */ @@ -55,6 +58,9 @@ struct radio_si4713_device { /* radio_si4713_fops - file operations interface */ static const struct v4l2_file_operations radio_si4713_fops = { .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, /* Note: locking is done at the subdev level in the i2c driver. */ .unlocked_ioctl = video_ioctl2, }; @@ -126,6 +132,9 @@ static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { .vidioc_s_modulator = radio_si4713_s_modulator, .vidioc_g_frequency = radio_si4713_g_frequency, .vidioc_s_frequency = radio_si4713_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_default = radio_si4713_default, }; @@ -187,6 +196,7 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) rsdev->radio_dev = radio_si4713_vdev_template; rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev; rsdev->radio_dev.ctrl_handler = sd->ctrl_handler; + set_bit(V4L2_FL_USE_FH_PRIO, &rsdev->radio_dev.flags); /* Serialize all access to the si4713 */ rsdev->radio_dev.lock = &rsdev->lock; video_set_drvdata(&rsdev->radio_dev, rsdev); -- cgit v1.1 From 82cd0b278fddc1c0bc7e187ff82fd0e273520233 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Wed, 17 Apr 2013 06:05:18 -0300 Subject: Revert "[media] v4l2: Add a V4L2 driver for SI476X MFD" As requested by Andrey Smirnov , revert this patch. This reverts commit 30bac9110455402fa8888740c6819dd3daa2666f. Conflicts: drivers/media/radio/Kconfig drivers/media/radio/radio-si476x.c Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/Kconfig | 17 - drivers/media/radio/Makefile | 1 - drivers/media/radio/radio-si476x.c | 1599 ------------------------------------ 3 files changed, 1617 deletions(-) delete mode 100644 drivers/media/radio/radio-si476x.c (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index fef427e..24e64a0 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,23 +18,6 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" -config RADIO_SI476X - tristate "Silicon Laboratories Si476x I2C FM Radio" - depends on I2C && VIDEO_V4L2 && SND && SND_SOC - select MFD_CORE - select MFD_SI476X_CORE - select SND_SOC_SI476X - ---help--- - Choose Y here if you have this FM radio chip. - - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux 2 API. Information on - this API and pointers to "v4l2" programs may be found at - . - - To compile this driver as a module, choose M here: the - module will be called radio-si476x. - config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 0dcdb32..303eaeb 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o -obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c deleted file mode 100644 index 9430c6a..0000000 --- a/drivers/media/radio/radio-si476x.c +++ /dev/null @@ -1,1599 +0,0 @@ -/* - * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips - * - * Copyright (C) 2012 Innovative Converged Devices(ICD) - * Copyright (C) 2013 Andrey Smirnov - * - * Author: Andrey Smirnov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * 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. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define FM_FREQ_RANGE_LOW 64000000 -#define FM_FREQ_RANGE_HIGH 108000000 - -#define AM_FREQ_RANGE_LOW 520000 -#define AM_FREQ_RANGE_HIGH 30000000 - -#define PWRLINEFLTR (1 << 8) - -#define FREQ_MUL (10000000 / 625) - -#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) - -#define DRIVER_NAME "si476x-radio" -#define DRIVER_CARD "SI476x AM/FM Receiver" - -enum si476x_freq_bands { - SI476X_BAND_FM, - SI476X_BAND_AM, -}; - -static const struct v4l2_frequency_band si476x_bands[] = { - [SI476X_BAND_FM] = { - .type = V4L2_TUNER_RADIO, - .index = SI476X_BAND_FM, - .capability = V4L2_TUNER_CAP_LOW - | V4L2_TUNER_CAP_STEREO - | V4L2_TUNER_CAP_RDS - | V4L2_TUNER_CAP_RDS_BLOCK_IO - | V4L2_TUNER_CAP_FREQ_BANDS, - .rangelow = 64 * FREQ_MUL, - .rangehigh = 108 * FREQ_MUL, - .modulation = V4L2_BAND_MODULATION_FM, - }, - [SI476X_BAND_AM] = { - .type = V4L2_TUNER_RADIO, - .index = SI476X_BAND_AM, - .capability = V4L2_TUNER_CAP_LOW - | V4L2_TUNER_CAP_FREQ_BANDS, - .rangelow = 0.52 * FREQ_MUL, - .rangehigh = 30 * FREQ_MUL, - .modulation = V4L2_BAND_MODULATION_AM, - }, -}; - -static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) -{ - return freq >= si476x_bands[band].rangelow && - freq <= si476x_bands[band].rangehigh; -} - -static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, - int band) -{ - return low >= si476x_bands[band].rangelow && - high <= si476x_bands[band].rangehigh; -} - -static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); -static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); - -enum phase_diversity_modes_idx { - SI476X_IDX_PHDIV_DISABLED, - SI476X_IDX_PHDIV_PRIMARY_COMBINING, - SI476X_IDX_PHDIV_PRIMARY_ANTENNA, - SI476X_IDX_PHDIV_SECONDARY_ANTENNA, - SI476X_IDX_PHDIV_SECONDARY_COMBINING, -}; - -static const char * const phase_diversity_modes[] = { - [SI476X_IDX_PHDIV_DISABLED] = "Disabled", - [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", - [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", - [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", - [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", -}; - -static inline enum phase_diversity_modes_idx -si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) -{ - switch (mode) { - default: /* FALLTHROUGH */ - case SI476X_PHDIV_DISABLED: - return SI476X_IDX_PHDIV_DISABLED; - case SI476X_PHDIV_PRIMARY_COMBINING: - return SI476X_IDX_PHDIV_PRIMARY_COMBINING; - case SI476X_PHDIV_PRIMARY_ANTENNA: - return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; - case SI476X_PHDIV_SECONDARY_ANTENNA: - return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; - case SI476X_PHDIV_SECONDARY_COMBINING: - return SI476X_IDX_PHDIV_SECONDARY_COMBINING; - } -} - -static inline enum si476x_phase_diversity_mode -si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) -{ - static const int idx_to_value[] = { - [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, - [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, - [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, - [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, - [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, - }; - - return idx_to_value[idx]; -} - -static const struct v4l2_ctrl_ops si476x_ctrl_ops = { - .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, - .s_ctrl = si476x_radio_s_ctrl, -}; - - -enum si476x_ctrl_idx { - SI476X_IDX_RSSI_THRESHOLD, - SI476X_IDX_SNR_THRESHOLD, - SI476X_IDX_MAX_TUNE_ERROR, - SI476X_IDX_HARMONICS_COUNT, - SI476X_IDX_DIVERSITY_MODE, - SI476X_IDX_INTERCHIP_LINK, -}; -static struct v4l2_ctrl_config si476x_ctrls[] = { - - /** - * SI476X during its station seeking(or tuning) process uses several - * parameters to detrmine if "the station" is valid: - * - * - Signal's SNR(in dBuV) must be lower than - * #V4L2_CID_SI476X_SNR_THRESHOLD - * - Signal's RSSI(in dBuV) must be greater than - * #V4L2_CID_SI476X_RSSI_THRESHOLD - * - Signal's frequency deviation(in units of 2ppm) must not be - * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR - */ - [SI476X_IDX_RSSI_THRESHOLD] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_RSSI_THRESHOLD, - .name = "Valid RSSI Threshold", - .type = V4L2_CTRL_TYPE_INTEGER, - .min = -128, - .max = 127, - .step = 1, - }, - [SI476X_IDX_SNR_THRESHOLD] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_SNR_THRESHOLD, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Valid SNR Threshold", - .min = -128, - .max = 127, - .step = 1, - }, - [SI476X_IDX_MAX_TUNE_ERROR] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Max Tune Errors", - .min = 0, - .max = 126 * 2, - .step = 2, - }, - - /** - * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics - * built-in power-line noise supression filter is to reject - * during AM-mode operation. - */ - [SI476X_IDX_HARMONICS_COUNT] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_HARMONICS_COUNT, - .type = V4L2_CTRL_TYPE_INTEGER, - - .name = "Count of Harmonics to Reject", - .min = 0, - .max = 20, - .step = 1, - }, - - /** - * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which - * two tuners working in diversity mode are to work in. - * - * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled - * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is - * on, primary tuner's antenna is the main one. - * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is - * off, primary tuner's antenna is the main one. - * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is - * off, secondary tuner's antenna is the main one. - * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is - * on, secondary tuner's antenna is the main one. - */ - [SI476X_IDX_DIVERSITY_MODE] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_DIVERSITY_MODE, - .type = V4L2_CTRL_TYPE_MENU, - .name = "Phase Diversity Mode", - .qmenu = phase_diversity_modes, - .min = 0, - .max = ARRAY_SIZE(phase_diversity_modes) - 1, - }, - - /** - * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in - * diversity mode indicator. Allows user to determine if two - * chips working in diversity mode have established a link - * between each other and if the system as a whole uses - * signals from both antennas to receive FM radio. - */ - [SI476X_IDX_INTERCHIP_LINK] = { - .ops = &si476x_ctrl_ops, - .id = V4L2_CID_SI476X_INTERCHIP_LINK, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, - .name = "Inter-Chip Link", - .min = 0, - .max = 1, - .step = 1, - }, -}; - -struct si476x_radio; - -/** - * struct si476x_radio_ops - vtable of tuner functions - * - * This table holds pointers to functions implementing particular - * operations depending on the mode in which the tuner chip was - * configured to start in. If the function is not supported - * corresponding element is set to #NULL. - * - * @tune_freq: Tune chip to a specific frequency - * @seek_start: Star station seeking - * @rsq_status: Get Recieved Signal Quality(RSQ) status - * @rds_blckcnt: Get recived RDS blocks count - * @phase_diversity: Change phase diversity mode of the tuner - * @phase_div_status: Get phase diversity mode status - * @acf_status: Get the status of Automatically Controlled - * Features(ACF) - * @agc_status: Get Automatic Gain Control(AGC) status - */ -struct si476x_radio_ops { - int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); - int (*seek_start)(struct si476x_core *, bool, bool); - int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, - struct si476x_rsq_status_report *); - int (*rds_blckcnt)(struct si476x_core *, bool, - struct si476x_rds_blockcount_report *); - - int (*phase_diversity)(struct si476x_core *, - enum si476x_phase_diversity_mode); - int (*phase_div_status)(struct si476x_core *); - int (*acf_status)(struct si476x_core *, - struct si476x_acf_status_report *); - int (*agc_status)(struct si476x_core *, - struct si476x_agc_status_report *); -}; - -/** - * struct si476x_radio - radio device - * - * @core: Pointer to underlying core device - * @videodev: Pointer to video device created by V4L2 subsystem - * @ops: Vtable of functions. See struct si476x_radio_ops for details - * @kref: Reference counter - * @core_lock: An r/w semaphore to brebvent the deletion of underlying - * core structure is the radio device is being used - */ -struct si476x_radio { - struct v4l2_device v4l2dev; - struct video_device videodev; - struct v4l2_ctrl_handler ctrl_handler; - - struct si476x_core *core; - /* This field should not be accesses unless core lock is held */ - const struct si476x_radio_ops *ops; - - struct dentry *debugfs; - u32 audmode; -}; - -static inline struct si476x_radio * -v4l2_dev_to_radio(struct v4l2_device *d) -{ - return container_of(d, struct si476x_radio, v4l2dev); -} - -static inline struct si476x_radio * -v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) -{ - return container_of(d, struct si476x_radio, ctrl_handler); -} - -/* - * si476x_vidioc_querycap - query device capabilities - */ -static int si476x_radio_querycap(struct file *file, void *priv, - struct v4l2_capability *capability) -{ - struct si476x_radio *radio = video_drvdata(file); - - strlcpy(capability->driver, radio->v4l2dev.name, - sizeof(capability->driver)); - strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); - snprintf(capability->bus_info, sizeof(capability->bus_info), - "platform:%s", radio->v4l2dev.name); - - capability->device_caps = V4L2_CAP_TUNER - | V4L2_CAP_RADIO - | V4L2_CAP_HW_FREQ_SEEK; - - si476x_core_lock(radio->core); - if (!si476x_core_is_a_secondary_tuner(radio->core)) - capability->device_caps |= V4L2_CAP_RDS_CAPTURE - | V4L2_CAP_READWRITE; - si476x_core_unlock(radio->core); - - capability->capabilities = capability->device_caps - | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -static int si476x_radio_enum_freq_bands(struct file *file, void *priv, - struct v4l2_frequency_band *band) -{ - int err; - struct si476x_radio *radio = video_drvdata(file); - - if (band->tuner != 0) - return -EINVAL; - - switch (radio->core->chip_id) { - /* AM/FM tuners -- all bands are supported */ - case SI476X_CHIP_SI4761: - case SI476X_CHIP_SI4764: - if (band->index < ARRAY_SIZE(si476x_bands)) { - *band = si476x_bands[band->index]; - err = 0; - } else { - err = -EINVAL; - } - break; - /* FM companion tuner chips -- only FM bands are - * supported */ - case SI476X_CHIP_SI4768: - if (band->index == SI476X_BAND_FM) { - *band = si476x_bands[band->index]; - err = 0; - } else { - err = -EINVAL; - } - break; - default: - err = -EINVAL; - } - - return err; -} - -static int si476x_radio_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) -{ - int err; - struct si476x_rsq_status_report report; - struct si476x_radio *radio = video_drvdata(file); - - struct si476x_rsq_status_args args = { - .primary = false, - .rsqack = false, - .attune = false, - .cancel = false, - .stcack = false, - }; - - if (tuner->index != 0) - return -EINVAL; - - tuner->type = V4L2_TUNER_RADIO; - tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies - * in multiples of - * 62.5 Hz */ - | V4L2_TUNER_CAP_STEREO - | V4L2_TUNER_CAP_HWSEEK_BOUNDED - | V4L2_TUNER_CAP_HWSEEK_WRAP - | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; - - si476x_core_lock(radio->core); - - if (si476x_core_is_a_secondary_tuner(radio->core)) { - strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); - tuner->rxsubchans = 0; - tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; - } else if (si476x_core_has_am(radio->core)) { - if (si476x_core_is_a_primary_tuner(radio->core)) - strlcpy(tuner->name, "AM/FM (primary)", - sizeof(tuner->name)); - else - strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); - - tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO - | V4L2_TUNER_SUB_RDS; - tuner->capability |= V4L2_TUNER_CAP_RDS - | V4L2_TUNER_CAP_RDS_BLOCK_IO - | V4L2_TUNER_CAP_FREQ_BANDS; - - tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; - } else { - strlcpy(tuner->name, "FM", sizeof(tuner->name)); - tuner->rxsubchans = V4L2_TUNER_SUB_RDS; - tuner->capability |= V4L2_TUNER_CAP_RDS - | V4L2_TUNER_CAP_RDS_BLOCK_IO - | V4L2_TUNER_CAP_FREQ_BANDS; - tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; - } - - tuner->audmode = radio->audmode; - - tuner->afc = 1; - tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; - - err = radio->ops->rsq_status(radio->core, - &args, &report); - if (err < 0) { - tuner->signal = 0; - } else { - /* - * tuner->signal value range: 0x0000 .. 0xFFFF, - * report.rssi: -128 .. 127 - */ - tuner->signal = (report.rssi + 128) * 257; - } - si476x_core_unlock(radio->core); - - return err; -} - -static int si476x_radio_s_tuner(struct file *file, void *priv, - const struct v4l2_tuner *tuner) -{ - struct si476x_radio *radio = video_drvdata(file); - - if (tuner->index != 0) - return -EINVAL; - - if (tuner->audmode == V4L2_TUNER_MODE_MONO || - tuner->audmode == V4L2_TUNER_MODE_STEREO) - radio->audmode = tuner->audmode; - else - radio->audmode = V4L2_TUNER_MODE_STEREO; - - return 0; -} - -static int si476x_radio_init_vtable(struct si476x_radio *radio, - enum si476x_func func) -{ - static const struct si476x_radio_ops fm_ops = { - .tune_freq = si476x_core_cmd_fm_tune_freq, - .seek_start = si476x_core_cmd_fm_seek_start, - .rsq_status = si476x_core_cmd_fm_rsq_status, - .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, - .phase_diversity = si476x_core_cmd_fm_phase_diversity, - .phase_div_status = si476x_core_cmd_fm_phase_div_status, - .acf_status = si476x_core_cmd_fm_acf_status, - .agc_status = si476x_core_cmd_agc_status, - }; - - static const struct si476x_radio_ops am_ops = { - .tune_freq = si476x_core_cmd_am_tune_freq, - .seek_start = si476x_core_cmd_am_seek_start, - .rsq_status = si476x_core_cmd_am_rsq_status, - .rds_blckcnt = NULL, - .phase_diversity = NULL, - .phase_div_status = NULL, - .acf_status = si476x_core_cmd_am_acf_status, - .agc_status = NULL, - }; - - switch (func) { - case SI476X_FUNC_FM_RECEIVER: - radio->ops = &fm_ops; - return 0; - - case SI476X_FUNC_AM_RECEIVER: - radio->ops = &am_ops; - return 0; - default: - WARN(1, "Unexpected tuner function value\n"); - return -EINVAL; - } -} - -static int si476x_radio_pretune(struct si476x_radio *radio, - enum si476x_func func) -{ - int retval; - - struct si476x_tune_freq_args args = { - .zifsr = false, - .hd = false, - .injside = SI476X_INJSIDE_AUTO, - .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, - .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, - .antcap = 0, - }; - - switch (func) { - case SI476X_FUNC_FM_RECEIVER: - args.freq = v4l2_to_si476x(radio->core, - 92 * FREQ_MUL); - retval = radio->ops->tune_freq(radio->core, &args); - break; - case SI476X_FUNC_AM_RECEIVER: - args.freq = v4l2_to_si476x(radio->core, - 0.6 * FREQ_MUL); - retval = radio->ops->tune_freq(radio->core, &args); - break; - default: - WARN(1, "Unexpected tuner function value\n"); - retval = -EINVAL; - } - - return retval; -} -static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, - enum si476x_func func) -{ - int err; - - /* regcache_mark_dirty(radio->core->regmap); */ - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, - SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); - if (err < 0) - return err; - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_AUDIO_DEEMPHASIS, - SI476X_PROP_AUDIO_PWR_LINE_FILTER); - if (err < 0) - return err; - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_INT_CTL_ENABLE, - SI476X_PROP_INT_CTL_ENABLE); - if (err < 0) - return err; - - /* - * Is there any point in restoring SNR and the like - * when switching between AM/FM? - */ - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_VALID_MAX_TUNE_ERROR, - SI476X_PROP_VALID_MAX_TUNE_ERROR); - if (err < 0) - return err; - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_VALID_SNR_THRESHOLD, - SI476X_PROP_VALID_RSSI_THRESHOLD); - if (err < 0) - return err; - - if (func == SI476X_FUNC_FM_RECEIVER) { - if (si476x_core_has_diversity(radio->core)) { - err = si476x_core_cmd_fm_phase_diversity(radio->core, - radio->core->diversity_mode); - if (err < 0) - return err; - } - - err = regcache_sync_region(radio->core->regmap, - SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, - SI476X_PROP_FM_RDS_CONFIG); - if (err < 0) - return err; - } - - return si476x_radio_init_vtable(radio, func); - -} - -static int si476x_radio_change_func(struct si476x_radio *radio, - enum si476x_func func) -{ - int err; - bool soft; - /* - * Since power/up down is a very time consuming operation, - * try to avoid doing it if the requested mode matches the one - * the tuner is in - */ - if (func == radio->core->power_up_parameters.func) - return 0; - - soft = true; - err = si476x_core_stop(radio->core, soft); - if (err < 0) { - /* - * OK, if the chip does not want to play nice let's - * try to reset it in more brutal way - */ - soft = false; - err = si476x_core_stop(radio->core, soft); - if (err < 0) - return err; - } - /* - Set the desired radio tuner function - */ - radio->core->power_up_parameters.func = func; - - err = si476x_core_start(radio->core, soft); - if (err < 0) - return err; - - /* - * No need to do the rest of manipulations for the bootlader - * mode - */ - if (func != SI476X_FUNC_FM_RECEIVER && - func != SI476X_FUNC_AM_RECEIVER) - return err; - - return si476x_radio_do_post_powerup_init(radio, func); -} - -static int si476x_radio_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - int err; - struct si476x_radio *radio = video_drvdata(file); - - if (f->tuner != 0 || - f->type != V4L2_TUNER_RADIO) - return -EINVAL; - - si476x_core_lock(radio->core); - - if (radio->ops->rsq_status) { - struct si476x_rsq_status_report report; - struct si476x_rsq_status_args args = { - .primary = false, - .rsqack = false, - .attune = true, - .cancel = false, - .stcack = false, - }; - - err = radio->ops->rsq_status(radio->core, &args, &report); - if (!err) - f->frequency = si476x_to_v4l2(radio->core, - report.readfreq); - } else { - err = -EINVAL; - } - - si476x_core_unlock(radio->core); - - return err; -} - -static int si476x_radio_s_frequency(struct file *file, void *priv, - const struct v4l2_frequency *f) -{ - int err; - u32 freq = f->frequency; - struct si476x_tune_freq_args args; - struct si476x_radio *radio = video_drvdata(file); - - const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + - si476x_bands[SI476X_BAND_FM].rangelow) / 2; - const int band = (freq > midrange) ? - SI476X_BAND_FM : SI476X_BAND_AM; - const enum si476x_func func = (band == SI476X_BAND_AM) ? - SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; - - if (f->tuner != 0 || - f->type != V4L2_TUNER_RADIO) - return -EINVAL; - - si476x_core_lock(radio->core); - - freq = clamp(freq, - si476x_bands[band].rangelow, - si476x_bands[band].rangehigh); - - if (si476x_radio_freq_is_inside_of_the_band(freq, - SI476X_BAND_AM) && - (!si476x_core_has_am(radio->core) || - si476x_core_is_a_secondary_tuner(radio->core))) { - err = -EINVAL; - goto unlock; - } - - err = si476x_radio_change_func(radio, func); - if (err < 0) - goto unlock; - - args.zifsr = false; - args.hd = false; - args.injside = SI476X_INJSIDE_AUTO; - args.freq = v4l2_to_si476x(radio->core, freq); - args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; - args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; - args.antcap = 0; - - err = radio->ops->tune_freq(radio->core, &args); - -unlock: - si476x_core_unlock(radio->core); - return err; -} - -static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, - const struct v4l2_hw_freq_seek *seek) -{ - int err; - enum si476x_func func; - u32 rangelow, rangehigh; - struct si476x_radio *radio = video_drvdata(file); - - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; - - if (seek->tuner != 0 || - seek->type != V4L2_TUNER_RADIO) - return -EINVAL; - - si476x_core_lock(radio->core); - - if (!seek->rangelow) { - err = regmap_read(radio->core->regmap, - SI476X_PROP_SEEK_BAND_BOTTOM, - &rangelow); - if (!err) - rangelow = si476x_to_v4l2(radio->core, rangelow); - else - goto unlock; - } - if (!seek->rangehigh) { - err = regmap_read(radio->core->regmap, - SI476X_PROP_SEEK_BAND_TOP, - &rangehigh); - if (!err) - rangehigh = si476x_to_v4l2(radio->core, rangehigh); - else - goto unlock; - } - - if (rangelow > rangehigh) { - err = -EINVAL; - goto unlock; - } - - if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, - SI476X_BAND_FM)) { - func = SI476X_FUNC_FM_RECEIVER; - - } else if (si476x_core_has_am(radio->core) && - si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, - SI476X_BAND_AM)) { - func = SI476X_FUNC_AM_RECEIVER; - } else { - err = -EINVAL; - goto unlock; - } - - err = si476x_radio_change_func(radio, func); - if (err < 0) - goto unlock; - - if (seek->rangehigh) { - err = regmap_write(radio->core->regmap, - SI476X_PROP_SEEK_BAND_TOP, - v4l2_to_si476x(radio->core, - seek->rangehigh)); - if (err) - goto unlock; - } - if (seek->rangelow) { - err = regmap_write(radio->core->regmap, - SI476X_PROP_SEEK_BAND_BOTTOM, - v4l2_to_si476x(radio->core, - seek->rangelow)); - if (err) - goto unlock; - } - if (seek->spacing) { - err = regmap_write(radio->core->regmap, - SI476X_PROP_SEEK_FREQUENCY_SPACING, - v4l2_to_si476x(radio->core, - seek->spacing)); - if (err) - goto unlock; - } - - err = radio->ops->seek_start(radio->core, - seek->seek_upward, - seek->wrap_around); -unlock: - si476x_core_unlock(radio->core); - - - - return err; -} - -static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -{ - int retval; - struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); - - si476x_core_lock(radio->core); - - switch (ctrl->id) { - case V4L2_CID_SI476X_INTERCHIP_LINK: - if (si476x_core_has_diversity(radio->core)) { - if (radio->ops->phase_diversity) { - retval = radio->ops->phase_div_status(radio->core); - if (retval < 0) - break; - - ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); - retval = 0; - break; - } else { - retval = -ENOTTY; - break; - } - } - retval = -EINVAL; - break; - default: - retval = -EINVAL; - break; - } - si476x_core_unlock(radio->core); - return retval; - -} - -static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) -{ - int retval; - enum si476x_phase_diversity_mode mode; - struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); - - si476x_core_lock(radio->core); - - switch (ctrl->id) { - case V4L2_CID_SI476X_HARMONICS_COUNT: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_HARMONICS_MASK, - ctrl->val); - break; - case V4L2_CID_POWER_LINE_FREQUENCY: - switch (ctrl->val) { - case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_ENABLE_MASK, - 0); - break; - case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_GRID_MASK, - SI476X_PROP_PWR_GRID_50HZ); - break; - case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_AUDIO_PWR_LINE_FILTER, - SI476X_PROP_PWR_GRID_MASK, - SI476X_PROP_PWR_GRID_60HZ); - break; - default: - retval = -EINVAL; - break; - } - break; - case V4L2_CID_SI476X_RSSI_THRESHOLD: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_VALID_RSSI_THRESHOLD, - ctrl->val); - break; - case V4L2_CID_SI476X_SNR_THRESHOLD: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_VALID_SNR_THRESHOLD, - ctrl->val); - break; - case V4L2_CID_SI476X_MAX_TUNE_ERROR: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_VALID_MAX_TUNE_ERROR, - ctrl->val); - break; - case V4L2_CID_RDS_RECEPTION: - /* - * It looks like RDS related properties are - * inaccesable when tuner is in AM mode, so cache the - * changes - */ - if (si476x_core_is_in_am_receiver_mode(radio->core)) - regcache_cache_only(radio->core->regmap, true); - - if (ctrl->val) { - retval = regmap_write(radio->core->regmap, - SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, - radio->core->rds_fifo_depth); - if (retval < 0) - break; - - if (radio->core->client->irq) { - retval = regmap_write(radio->core->regmap, - SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, - SI476X_RDSRECV); - if (retval < 0) - break; - } - - /* Drain RDS FIFO before enabling RDS processing */ - retval = si476x_core_cmd_fm_rds_status(radio->core, - false, - true, - true, - NULL); - if (retval < 0) - break; - - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_FM_RDS_CONFIG, - SI476X_PROP_RDSEN_MASK, - SI476X_PROP_RDSEN); - } else { - retval = regmap_update_bits(radio->core->regmap, - SI476X_PROP_FM_RDS_CONFIG, - SI476X_PROP_RDSEN_MASK, - !SI476X_PROP_RDSEN); - } - - if (si476x_core_is_in_am_receiver_mode(radio->core)) - regcache_cache_only(radio->core->regmap, false); - break; - case V4L2_CID_TUNE_DEEMPHASIS: - retval = regmap_write(radio->core->regmap, - SI476X_PROP_AUDIO_DEEMPHASIS, - ctrl->val); - break; - - case V4L2_CID_SI476X_DIVERSITY_MODE: - mode = si476x_phase_diversity_idx_to_mode(ctrl->val); - - if (mode == radio->core->diversity_mode) { - retval = 0; - break; - } - - if (si476x_core_is_in_am_receiver_mode(radio->core)) { - /* - * Diversity cannot be configured while tuner - * is in AM mode so save the changes and carry on. - */ - radio->core->diversity_mode = mode; - retval = 0; - } else { - retval = radio->ops->phase_diversity(radio->core, mode); - if (!retval) - radio->core->diversity_mode = mode; - } - break; - - default: - retval = -EINVAL; - break; - } - - si476x_core_unlock(radio->core); - - return retval; -} - -static int si476x_radio_g_chip_ident(struct file *file, void *fh, - struct v4l2_dbg_chip_ident *chip) -{ - if (chip->match.type == V4L2_CHIP_MATCH_HOST && - v4l2_chip_match_host(&chip->match)) - return 0; - return -EINVAL; -} - - -#ifdef CONFIG_VIDEO_ADV_DEBUG -static int si476x_radio_g_register(struct file *file, void *fh, - struct v4l2_dbg_register *reg) -{ - int err; - unsigned int value; - struct si476x_radio *radio = video_drvdata(file); - - si476x_core_lock(radio->core); - reg->size = 2; - err = regmap_read(radio->core->regmap, - (unsigned int)reg->reg, &value); - reg->val = value; - si476x_core_unlock(radio->core); - - return err; -} -static int si476x_radio_s_register(struct file *file, void *fh, - const struct v4l2_dbg_register *reg) -{ - - int err; - struct si476x_radio *radio = video_drvdata(file); - - si476x_core_lock(radio->core); - err = regmap_write(radio->core->regmap, - (unsigned int)reg->reg, - (unsigned int)reg->val); - si476x_core_unlock(radio->core); - - return err; -} -#endif - -static int si476x_radio_fops_open(struct file *file) -{ - struct si476x_radio *radio = video_drvdata(file); - int err; - - err = v4l2_fh_open(file); - if (err) - return err; - - if (v4l2_fh_is_singular_file(file)) { - si476x_core_lock(radio->core); - err = si476x_core_set_power_state(radio->core, - SI476X_POWER_UP_FULL); - if (err < 0) - goto done; - - err = si476x_radio_do_post_powerup_init(radio, - radio->core->power_up_parameters.func); - if (err < 0) - goto power_down; - - err = si476x_radio_pretune(radio, - radio->core->power_up_parameters.func); - if (err < 0) - goto power_down; - - si476x_core_unlock(radio->core); - /*Must be done after si476x_core_unlock to prevent a deadlock*/ - v4l2_ctrl_handler_setup(&radio->ctrl_handler); - } - - return err; - -power_down: - si476x_core_set_power_state(radio->core, - SI476X_POWER_DOWN); -done: - si476x_core_unlock(radio->core); - v4l2_fh_release(file); - - return err; -} - -static int si476x_radio_fops_release(struct file *file) -{ - int err; - struct si476x_radio *radio = video_drvdata(file); - - if (v4l2_fh_is_singular_file(file) && - atomic_read(&radio->core->is_alive)) - si476x_core_set_power_state(radio->core, - SI476X_POWER_DOWN); - - err = v4l2_fh_release(file); - - return err; -} - -static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - ssize_t rval; - size_t fifo_len; - unsigned int copied; - - struct si476x_radio *radio = video_drvdata(file); - - /* block if no new data available */ - if (kfifo_is_empty(&radio->core->rds_fifo)) { - if (file->f_flags & O_NONBLOCK) - return -EWOULDBLOCK; - - rval = wait_event_interruptible(radio->core->rds_read_queue, - (!kfifo_is_empty(&radio->core->rds_fifo) || - !atomic_read(&radio->core->is_alive))); - if (rval < 0) - return -EINTR; - - if (!atomic_read(&radio->core->is_alive)) - return -ENODEV; - } - - fifo_len = kfifo_len(&radio->core->rds_fifo); - - if (kfifo_to_user(&radio->core->rds_fifo, buf, - min(fifo_len, count), - &copied) != 0) { - dev_warn(&radio->videodev.dev, - "Error during FIFO to userspace copy\n"); - rval = -EIO; - } else { - rval = (ssize_t)copied; - } - - return rval; -} - -static unsigned int si476x_radio_fops_poll(struct file *file, - struct poll_table_struct *pts) -{ - struct si476x_radio *radio = video_drvdata(file); - unsigned long req_events = poll_requested_events(pts); - unsigned int err = v4l2_ctrl_poll(file, pts); - - if (req_events & (POLLIN | POLLRDNORM)) { - if (atomic_read(&radio->core->is_alive)) - poll_wait(file, &radio->core->rds_read_queue, pts); - - if (!atomic_read(&radio->core->is_alive)) - err = POLLHUP; - - if (!kfifo_is_empty(&radio->core->rds_fifo)) - err = POLLIN | POLLRDNORM; - } - - return err; -} - -static const struct v4l2_file_operations si476x_fops = { - .owner = THIS_MODULE, - .read = si476x_radio_fops_read, - .poll = si476x_radio_fops_poll, - .unlocked_ioctl = video_ioctl2, - .open = si476x_radio_fops_open, - .release = si476x_radio_fops_release, -}; - - -static const struct v4l2_ioctl_ops si4761_ioctl_ops = { - .vidioc_querycap = si476x_radio_querycap, - .vidioc_g_tuner = si476x_radio_g_tuner, - .vidioc_s_tuner = si476x_radio_s_tuner, - - .vidioc_g_frequency = si476x_radio_g_frequency, - .vidioc_s_frequency = si476x_radio_s_frequency, - .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, - .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, - - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, - - .vidioc_g_chip_ident = si476x_radio_g_chip_ident, -#ifdef CONFIG_VIDEO_ADV_DEBUG - .vidioc_g_register = si476x_radio_g_register, - .vidioc_s_register = si476x_radio_s_register, -#endif -}; - - -static const struct video_device si476x_viddev_template = { - .fops = &si476x_fops, - .name = DRIVER_NAME, - .release = video_device_release_empty, -}; - - - -static ssize_t si476x_radio_read_acf_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_acf_status_report report; - - si476x_core_lock(radio->core); - if (radio->ops->acf_status) - err = radio->ops->acf_status(radio->core, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_acf_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_acf_blob, -}; - -static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_rds_blockcount_report report; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->rds_blckcnt(radio->core, true, - &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_rds_blckcnt_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_rds_blckcnt_blob, -}; - -static ssize_t si476x_radio_read_agc_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_agc_status_report report; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->agc_status(radio->core, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_agc_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_agc_blob, -}; - -static ssize_t si476x_radio_read_rsq_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_rsq_status_report report; - struct si476x_rsq_status_args args = { - .primary = false, - .rsqack = false, - .attune = false, - .cancel = false, - .stcack = false, - }; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->rsq_status(radio->core, &args, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_rsq_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_rsq_blob, -}; - -static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - int err; - struct si476x_radio *radio = file->private_data; - struct si476x_rsq_status_report report; - struct si476x_rsq_status_args args = { - .primary = true, - .rsqack = false, - .attune = false, - .cancel = false, - .stcack = false, - }; - - si476x_core_lock(radio->core); - if (radio->ops->rds_blckcnt) - err = radio->ops->rsq_status(radio->core, &args, &report); - else - err = -ENOENT; - si476x_core_unlock(radio->core); - - if (err < 0) - return err; - - return simple_read_from_buffer(user_buf, count, ppos, &report, - sizeof(report)); -} - -static const struct file_operations radio_rsq_primary_fops = { - .open = simple_open, - .llseek = default_llseek, - .read = si476x_radio_read_rsq_primary_blob, -}; - - -static int si476x_radio_init_debugfs(struct si476x_radio *radio) -{ - struct dentry *dentry; - int ret; - - dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto exit; - } - radio->debugfs = dentry; - - dentry = debugfs_create_file("acf", S_IRUGO, - radio->debugfs, radio, &radio_acf_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, - radio->debugfs, radio, - &radio_rds_blckcnt_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("agc", S_IRUGO, - radio->debugfs, radio, &radio_agc_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("rsq", S_IRUGO, - radio->debugfs, radio, &radio_rsq_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - dentry = debugfs_create_file("rsq_primary", S_IRUGO, - radio->debugfs, radio, - &radio_rsq_primary_fops); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto cleanup; - } - - return 0; -cleanup: - debugfs_remove_recursive(radio->debugfs); -exit: - return ret; -} - - -static int si476x_radio_add_new_custom(struct si476x_radio *radio, - enum si476x_ctrl_idx idx) -{ - int rval; - struct v4l2_ctrl *ctrl; - - ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, - &si476x_ctrls[idx], - NULL); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) - dev_err(radio->v4l2dev.dev, - "Could not initialize '%s' control %d\n", - si476x_ctrls[idx].name, rval); - - return rval; -} - -static int si476x_radio_probe(struct platform_device *pdev) -{ - int rval; - struct si476x_radio *radio; - struct v4l2_ctrl *ctrl; - - static atomic_t instance = ATOMIC_INIT(0); - - radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); - if (!radio) - return -ENOMEM; - - radio->core = i2c_mfd_cell_to_core(&pdev->dev); - - v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); - - rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); - if (rval) { - dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); - return rval; - } - - memcpy(&radio->videodev, &si476x_viddev_template, - sizeof(struct video_device)); - - radio->videodev.v4l2_dev = &radio->v4l2dev; - radio->videodev.ioctl_ops = &si4761_ioctl_ops; - - video_set_drvdata(&radio->videodev, radio); - platform_set_drvdata(pdev, radio); - - set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); - - radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; - v4l2_ctrl_handler_init(&radio->ctrl_handler, - 1 + ARRAY_SIZE(si476x_ctrls)); - - if (si476x_core_has_am(radio->core)) { - ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, - &si476x_ctrl_ops, - V4L2_CID_POWER_LINE_FREQUENCY, - V4L2_CID_POWER_LINE_FREQUENCY_60HZ, - 0, 0); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) { - dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", - rval); - goto exit; - } - - rval = si476x_radio_add_new_custom(radio, - SI476X_IDX_HARMONICS_COUNT); - if (rval < 0) - goto exit; - } - - rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); - if (rval < 0) - goto exit; - - rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); - if (rval < 0) - goto exit; - - rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); - if (rval < 0) - goto exit; - - ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, - &si476x_ctrl_ops, - V4L2_CID_TUNE_DEEMPHASIS, - V4L2_DEEMPHASIS_75_uS, 0, 0); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) { - dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", - rval); - goto exit; - } - - ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, - V4L2_CID_RDS_RECEPTION, - 0, 1, 1, 1); - rval = radio->ctrl_handler.error; - if (ctrl == NULL && rval) { - dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", - rval); - goto exit; - } - - if (si476x_core_has_diversity(radio->core)) { - si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = - si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); - si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); - if (rval < 0) - goto exit; - - si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); - if (rval < 0) - goto exit; - } - - /* register video device */ - rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); - if (rval < 0) { - dev_err(&pdev->dev, "Could not register video device\n"); - goto exit; - } - - rval = si476x_radio_init_debugfs(radio); - if (rval < 0) { - dev_err(&pdev->dev, "Could not creat debugfs interface\n"); - goto exit; - } - - return 0; -exit: - v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); - return rval; -} - -static int si476x_radio_remove(struct platform_device *pdev) -{ - struct si476x_radio *radio = platform_get_drvdata(pdev); - - v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); - video_unregister_device(&radio->videodev); - v4l2_device_unregister(&radio->v4l2dev); - debugfs_remove_recursive(radio->debugfs); - - return 0; -} - -MODULE_ALIAS("platform:si476x-radio"); - -static struct platform_driver si476x_radio_driver = { - .driver = { - .name = DRIVER_NAME, - .owner = THIS_MODULE, - }, - .probe = si476x_radio_probe, - .remove = si476x_radio_remove, -}; -module_platform_driver(si476x_radio_driver); - -MODULE_AUTHOR("Andrey Smirnov "); -MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); -MODULE_LICENSE("GPL"); -- cgit v1.1 From b879a9c2a755d4ddf9e685258de6435710fd2f03 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 18 Apr 2013 20:46:08 -0300 Subject: [media] v4l2: Add a V4L2 driver for SI476X MFD This commit adds a driver that exposes all the radio related functionality of the Si476x series of chips via the V4L2 subsystem. [mchehab@redhat.com: change it to depends on MFD_SI476X_CORE instead of selecting it; vidioc_s_register now uses const struct] Acked-by: Hans Verkuil Signed-off-by: Andrey Smirnov Signed-off-by: Mauro Carvalho Chehab --- drivers/media/radio/Kconfig | 16 + drivers/media/radio/Makefile | 1 + drivers/media/radio/radio-si476x.c | 1599 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1616 insertions(+) create mode 100644 drivers/media/radio/radio-si476x.c (limited to 'drivers/media/radio') diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 24e64a0..c0beee2 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,6 +18,22 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI476X + tristate "Silicon Laboratories Si476x I2C FM Radio" + depends on I2C && VIDEO_V4L2 + depends on MFD_SI476X_CORE + select SND_SOC_SI476X + ---help--- + Choose Y here if you have this FM radio chip. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux 2 API. Information on + this API and pointers to "v4l2" programs may be found at + . + + To compile this driver as a module, choose M here: the + module will be called radio-si476x. + config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 303eaeb..0dcdb32 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o +obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c new file mode 100644 index 0000000..9430c6a --- /dev/null +++ b/drivers/media/radio/radio-si476x.c @@ -0,0 +1,1599 @@ +/* + * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FM_FREQ_RANGE_LOW 64000000 +#define FM_FREQ_RANGE_HIGH 108000000 + +#define AM_FREQ_RANGE_LOW 520000 +#define AM_FREQ_RANGE_HIGH 30000000 + +#define PWRLINEFLTR (1 << 8) + +#define FREQ_MUL (10000000 / 625) + +#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) + +#define DRIVER_NAME "si476x-radio" +#define DRIVER_CARD "SI476x AM/FM Receiver" + +enum si476x_freq_bands { + SI476X_BAND_FM, + SI476X_BAND_AM, +}; + +static const struct v4l2_frequency_band si476x_bands[] = { + [SI476X_BAND_FM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 64 * FREQ_MUL, + .rangehigh = 108 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_FM, + }, + [SI476X_BAND_AM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_AM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 0.52 * FREQ_MUL, + .rangehigh = 30 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) +{ + return freq >= si476x_bands[band].rangelow && + freq <= si476x_bands[band].rangehigh; +} + +static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, + int band) +{ + return low >= si476x_bands[band].rangelow && + high <= si476x_bands[band].rangehigh; +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); + +enum phase_diversity_modes_idx { + SI476X_IDX_PHDIV_DISABLED, + SI476X_IDX_PHDIV_PRIMARY_COMBINING, + SI476X_IDX_PHDIV_PRIMARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_COMBINING, +}; + +static const char * const phase_diversity_modes[] = { + [SI476X_IDX_PHDIV_DISABLED] = "Disabled", + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", +}; + +static inline enum phase_diversity_modes_idx +si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) +{ + switch (mode) { + default: /* FALLTHROUGH */ + case SI476X_PHDIV_DISABLED: + return SI476X_IDX_PHDIV_DISABLED; + case SI476X_PHDIV_PRIMARY_COMBINING: + return SI476X_IDX_PHDIV_PRIMARY_COMBINING; + case SI476X_PHDIV_PRIMARY_ANTENNA: + return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_ANTENNA: + return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_COMBINING: + return SI476X_IDX_PHDIV_SECONDARY_COMBINING; + } +} + +static inline enum si476x_phase_diversity_mode +si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) +{ + static const int idx_to_value[] = { + [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, + }; + + return idx_to_value[idx]; +} + +static const struct v4l2_ctrl_ops si476x_ctrl_ops = { + .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, + .s_ctrl = si476x_radio_s_ctrl, +}; + + +enum si476x_ctrl_idx { + SI476X_IDX_RSSI_THRESHOLD, + SI476X_IDX_SNR_THRESHOLD, + SI476X_IDX_MAX_TUNE_ERROR, + SI476X_IDX_HARMONICS_COUNT, + SI476X_IDX_DIVERSITY_MODE, + SI476X_IDX_INTERCHIP_LINK, +}; +static struct v4l2_ctrl_config si476x_ctrls[] = { + + /** + * SI476X during its station seeking(or tuning) process uses several + * parameters to detrmine if "the station" is valid: + * + * - Signal's SNR(in dBuV) must be lower than + * #V4L2_CID_SI476X_SNR_THRESHOLD + * - Signal's RSSI(in dBuV) must be greater than + * #V4L2_CID_SI476X_RSSI_THRESHOLD + * - Signal's frequency deviation(in units of 2ppm) must not be + * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR + */ + [SI476X_IDX_RSSI_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_RSSI_THRESHOLD, + .name = "Valid RSSI Threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_SNR_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_SNR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Valid SNR Threshold", + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_MAX_TUNE_ERROR] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Max Tune Errors", + .min = 0, + .max = 126 * 2, + .step = 2, + }, + + /** + * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics + * built-in power-line noise supression filter is to reject + * during AM-mode operation. + */ + [SI476X_IDX_HARMONICS_COUNT] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_HARMONICS_COUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + + .name = "Count of Harmonics to Reject", + .min = 0, + .max = 20, + .step = 1, + }, + + /** + * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which + * two tuners working in diversity mode are to work in. + * + * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled + * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is + * on, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is + * off, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is + * off, secondary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is + * on, secondary tuner's antenna is the main one. + */ + [SI476X_IDX_DIVERSITY_MODE] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_DIVERSITY_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Phase Diversity Mode", + .qmenu = phase_diversity_modes, + .min = 0, + .max = ARRAY_SIZE(phase_diversity_modes) - 1, + }, + + /** + * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in + * diversity mode indicator. Allows user to determine if two + * chips working in diversity mode have established a link + * between each other and if the system as a whole uses + * signals from both antennas to receive FM radio. + */ + [SI476X_IDX_INTERCHIP_LINK] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_INTERCHIP_LINK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, + .name = "Inter-Chip Link", + .min = 0, + .max = 1, + .step = 1, + }, +}; + +struct si476x_radio; + +/** + * struct si476x_radio_ops - vtable of tuner functions + * + * This table holds pointers to functions implementing particular + * operations depending on the mode in which the tuner chip was + * configured to start in. If the function is not supported + * corresponding element is set to #NULL. + * + * @tune_freq: Tune chip to a specific frequency + * @seek_start: Star station seeking + * @rsq_status: Get Recieved Signal Quality(RSQ) status + * @rds_blckcnt: Get recived RDS blocks count + * @phase_diversity: Change phase diversity mode of the tuner + * @phase_div_status: Get phase diversity mode status + * @acf_status: Get the status of Automatically Controlled + * Features(ACF) + * @agc_status: Get Automatic Gain Control(AGC) status + */ +struct si476x_radio_ops { + int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); + int (*seek_start)(struct si476x_core *, bool, bool); + int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + int (*rds_blckcnt)(struct si476x_core *, bool, + struct si476x_rds_blockcount_report *); + + int (*phase_diversity)(struct si476x_core *, + enum si476x_phase_diversity_mode); + int (*phase_div_status)(struct si476x_core *); + int (*acf_status)(struct si476x_core *, + struct si476x_acf_status_report *); + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); +}; + +/** + * struct si476x_radio - radio device + * + * @core: Pointer to underlying core device + * @videodev: Pointer to video device created by V4L2 subsystem + * @ops: Vtable of functions. See struct si476x_radio_ops for details + * @kref: Reference counter + * @core_lock: An r/w semaphore to brebvent the deletion of underlying + * core structure is the radio device is being used + */ +struct si476x_radio { + struct v4l2_device v4l2dev; + struct video_device videodev; + struct v4l2_ctrl_handler ctrl_handler; + + struct si476x_core *core; + /* This field should not be accesses unless core lock is held */ + const struct si476x_radio_ops *ops; + + struct dentry *debugfs; + u32 audmode; +}; + +static inline struct si476x_radio * +v4l2_dev_to_radio(struct v4l2_device *d) +{ + return container_of(d, struct si476x_radio, v4l2dev); +} + +static inline struct si476x_radio * +v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) +{ + return container_of(d, struct si476x_radio, ctrl_handler); +} + +/* + * si476x_vidioc_querycap - query device capabilities + */ +static int si476x_radio_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct si476x_radio *radio = video_drvdata(file); + + strlcpy(capability->driver, radio->v4l2dev.name, + sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", radio->v4l2dev.name); + + capability->device_caps = V4L2_CAP_TUNER + | V4L2_CAP_RADIO + | V4L2_CAP_HW_FREQ_SEEK; + + si476x_core_lock(radio->core); + if (!si476x_core_is_a_secondary_tuner(radio->core)) + capability->device_caps |= V4L2_CAP_RDS_CAPTURE + | V4L2_CAP_READWRITE; + si476x_core_unlock(radio->core); + + capability->capabilities = capability->device_caps + | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int si476x_radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (band->tuner != 0) + return -EINVAL; + + switch (radio->core->chip_id) { + /* AM/FM tuners -- all bands are supported */ + case SI476X_CHIP_SI4761: + case SI476X_CHIP_SI4764: + if (band->index < ARRAY_SIZE(si476x_bands)) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + /* FM companion tuner chips -- only FM bands are + * supported */ + case SI476X_CHIP_SI4768: + if (band->index == SI476X_BAND_FM) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int err; + struct si476x_rsq_status_report report; + struct si476x_radio *radio = video_drvdata(file); + + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + if (tuner->index != 0) + return -EINVAL; + + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies + * in multiples of + * 62.5 Hz */ + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_HWSEEK_BOUNDED + | V4L2_TUNER_CAP_HWSEEK_WRAP + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + + si476x_core_lock(radio->core); + + if (si476x_core_is_a_secondary_tuner(radio->core)) { + strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); + tuner->rxsubchans = 0; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } else if (si476x_core_has_am(radio->core)) { + if (si476x_core_is_a_primary_tuner(radio->core)) + strlcpy(tuner->name, "AM/FM (primary)", + sizeof(tuner->name)); + else + strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); + + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO + | V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + + tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; + } else { + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } + + tuner->audmode = radio->audmode; + + tuner->afc = 1; + tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; + + err = radio->ops->rsq_status(radio->core, + &args, &report); + if (err < 0) { + tuner->signal = 0; + } else { + /* + * tuner->signal value range: 0x0000 .. 0xFFFF, + * report.rssi: -128 .. 127 + */ + tuner->signal = (report.rssi + 128) * 257; + } + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct si476x_radio *radio = video_drvdata(file); + + if (tuner->index != 0) + return -EINVAL; + + if (tuner->audmode == V4L2_TUNER_MODE_MONO || + tuner->audmode == V4L2_TUNER_MODE_STEREO) + radio->audmode = tuner->audmode; + else + radio->audmode = V4L2_TUNER_MODE_STEREO; + + return 0; +} + +static int si476x_radio_init_vtable(struct si476x_radio *radio, + enum si476x_func func) +{ + static const struct si476x_radio_ops fm_ops = { + .tune_freq = si476x_core_cmd_fm_tune_freq, + .seek_start = si476x_core_cmd_fm_seek_start, + .rsq_status = si476x_core_cmd_fm_rsq_status, + .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, + .phase_diversity = si476x_core_cmd_fm_phase_diversity, + .phase_div_status = si476x_core_cmd_fm_phase_div_status, + .acf_status = si476x_core_cmd_fm_acf_status, + .agc_status = si476x_core_cmd_agc_status, + }; + + static const struct si476x_radio_ops am_ops = { + .tune_freq = si476x_core_cmd_am_tune_freq, + .seek_start = si476x_core_cmd_am_seek_start, + .rsq_status = si476x_core_cmd_am_rsq_status, + .rds_blckcnt = NULL, + .phase_diversity = NULL, + .phase_div_status = NULL, + .acf_status = si476x_core_cmd_am_acf_status, + .agc_status = NULL, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + radio->ops = &fm_ops; + return 0; + + case SI476X_FUNC_AM_RECEIVER: + radio->ops = &am_ops; + return 0; + default: + WARN(1, "Unexpected tuner function value\n"); + return -EINVAL; + } +} + +static int si476x_radio_pretune(struct si476x_radio *radio, + enum si476x_func func) +{ + int retval; + + struct si476x_tune_freq_args args = { + .zifsr = false, + .hd = false, + .injside = SI476X_INJSIDE_AUTO, + .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, + .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, + .antcap = 0, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 92 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + case SI476X_FUNC_AM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 0.6 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + default: + WARN(1, "Unexpected tuner function value\n"); + retval = -EINVAL; + } + + return retval; +} +static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + + /* regcache_mark_dirty(radio->core->regmap); */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, + SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + SI476X_PROP_AUDIO_PWR_LINE_FILTER); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_PROP_INT_CTL_ENABLE); + if (err < 0) + return err; + + /* + * Is there any point in restoring SNR and the like + * when switching between AM/FM? + */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + SI476X_PROP_VALID_MAX_TUNE_ERROR); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + SI476X_PROP_VALID_RSSI_THRESHOLD); + if (err < 0) + return err; + + if (func == SI476X_FUNC_FM_RECEIVER) { + if (si476x_core_has_diversity(radio->core)) { + err = si476x_core_cmd_fm_phase_diversity(radio->core, + radio->core->diversity_mode); + if (err < 0) + return err; + } + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_PROP_FM_RDS_CONFIG); + if (err < 0) + return err; + } + + return si476x_radio_init_vtable(radio, func); + +} + +static int si476x_radio_change_func(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + bool soft; + /* + * Since power/up down is a very time consuming operation, + * try to avoid doing it if the requested mode matches the one + * the tuner is in + */ + if (func == radio->core->power_up_parameters.func) + return 0; + + soft = true; + err = si476x_core_stop(radio->core, soft); + if (err < 0) { + /* + * OK, if the chip does not want to play nice let's + * try to reset it in more brutal way + */ + soft = false; + err = si476x_core_stop(radio->core, soft); + if (err < 0) + return err; + } + /* + Set the desired radio tuner function + */ + radio->core->power_up_parameters.func = func; + + err = si476x_core_start(radio->core, soft); + if (err < 0) + return err; + + /* + * No need to do the rest of manipulations for the bootlader + * mode + */ + if (func != SI476X_FUNC_FM_RECEIVER && + func != SI476X_FUNC_AM_RECEIVER) + return err; + + return si476x_radio_do_post_powerup_init(radio, func); +} + +static int si476x_radio_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (radio->ops->rsq_status) { + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = true, + .cancel = false, + .stcack = false, + }; + + err = radio->ops->rsq_status(radio->core, &args, &report); + if (!err) + f->frequency = si476x_to_v4l2(radio->core, + report.readfreq); + } else { + err = -EINVAL; + } + + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + int err; + u32 freq = f->frequency; + struct si476x_tune_freq_args args; + struct si476x_radio *radio = video_drvdata(file); + + const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + + si476x_bands[SI476X_BAND_FM].rangelow) / 2; + const int band = (freq > midrange) ? + SI476X_BAND_FM : SI476X_BAND_AM; + const enum si476x_func func = (band == SI476X_BAND_AM) ? + SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + freq = clamp(freq, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); + + if (si476x_radio_freq_is_inside_of_the_band(freq, + SI476X_BAND_AM) && + (!si476x_core_has_am(radio->core) || + si476x_core_is_a_secondary_tuner(radio->core))) { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + args.zifsr = false; + args.hd = false; + args.injside = SI476X_INJSIDE_AUTO; + args.freq = v4l2_to_si476x(radio->core, freq); + args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; + args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; + args.antcap = 0; + + err = radio->ops->tune_freq(radio->core, &args); + +unlock: + si476x_core_unlock(radio->core); + return err; +} + +static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + int err; + enum si476x_func func; + u32 rangelow, rangehigh; + struct si476x_radio *radio = video_drvdata(file); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (seek->tuner != 0 || + seek->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (!seek->rangelow) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + &rangelow); + if (!err) + rangelow = si476x_to_v4l2(radio->core, rangelow); + else + goto unlock; + } + if (!seek->rangehigh) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + &rangehigh); + if (!err) + rangehigh = si476x_to_v4l2(radio->core, rangehigh); + else + goto unlock; + } + + if (rangelow > rangehigh) { + err = -EINVAL; + goto unlock; + } + + if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_FM)) { + func = SI476X_FUNC_FM_RECEIVER; + + } else if (si476x_core_has_am(radio->core) && + si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_AM)) { + func = SI476X_FUNC_AM_RECEIVER; + } else { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + if (seek->rangehigh) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + v4l2_to_si476x(radio->core, + seek->rangehigh)); + if (err) + goto unlock; + } + if (seek->rangelow) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + v4l2_to_si476x(radio->core, + seek->rangelow)); + if (err) + goto unlock; + } + if (seek->spacing) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_FREQUENCY_SPACING, + v4l2_to_si476x(radio->core, + seek->spacing)); + if (err) + goto unlock; + } + + err = radio->ops->seek_start(radio->core, + seek->seek_upward, + seek->wrap_around); +unlock: + si476x_core_unlock(radio->core); + + + + return err; +} + +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_INTERCHIP_LINK: + if (si476x_core_has_diversity(radio->core)) { + if (radio->ops->phase_diversity) { + retval = radio->ops->phase_div_status(radio->core); + if (retval < 0) + break; + + ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); + retval = 0; + break; + } else { + retval = -ENOTTY; + break; + } + } + retval = -EINVAL; + break; + default: + retval = -EINVAL; + break; + } + si476x_core_unlock(radio->core); + return retval; + +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + enum si476x_phase_diversity_mode mode; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_HARMONICS_COUNT: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_HARMONICS_MASK, + ctrl->val); + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + switch (ctrl->val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_ENABLE_MASK, + 0); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_50HZ); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_60HZ); + break; + default: + retval = -EINVAL; + break; + } + break; + case V4L2_CID_SI476X_RSSI_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_RSSI_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_SNR_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_MAX_TUNE_ERROR: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + ctrl->val); + break; + case V4L2_CID_RDS_RECEPTION: + /* + * It looks like RDS related properties are + * inaccesable when tuner is in AM mode, so cache the + * changes + */ + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, true); + + if (ctrl->val) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, + radio->core->rds_fifo_depth); + if (retval < 0) + break; + + if (radio->core->client->irq) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_RDSRECV); + if (retval < 0) + break; + } + + /* Drain RDS FIFO before enabling RDS processing */ + retval = si476x_core_cmd_fm_rds_status(radio->core, + false, + true, + true, + NULL); + if (retval < 0) + break; + + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + SI476X_PROP_RDSEN); + } else { + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + !SI476X_PROP_RDSEN); + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, false); + break; + case V4L2_CID_TUNE_DEEMPHASIS: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + ctrl->val); + break; + + case V4L2_CID_SI476X_DIVERSITY_MODE: + mode = si476x_phase_diversity_idx_to_mode(ctrl->val); + + if (mode == radio->core->diversity_mode) { + retval = 0; + break; + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) { + /* + * Diversity cannot be configured while tuner + * is in AM mode so save the changes and carry on. + */ + radio->core->diversity_mode = mode; + retval = 0; + } else { + retval = radio->ops->phase_diversity(radio->core, mode); + if (!retval) + radio->core->diversity_mode = mode; + } + break; + + default: + retval = -EINVAL; + break; + } + + si476x_core_unlock(radio->core); + + return retval; +} + +static int si476x_radio_g_chip_ident(struct file *file, void *fh, + struct v4l2_dbg_chip_ident *chip) +{ + if (chip->match.type == V4L2_CHIP_MATCH_HOST && + v4l2_chip_match_host(&chip->match)) + return 0; + return -EINVAL; +} + + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int si476x_radio_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + int err; + unsigned int value; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + reg->size = 2; + err = regmap_read(radio->core->regmap, + (unsigned int)reg->reg, &value); + reg->val = value; + si476x_core_unlock(radio->core); + + return err; +} +static int si476x_radio_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + + int err; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + err = regmap_write(radio->core->regmap, + (unsigned int)reg->reg, + (unsigned int)reg->val); + si476x_core_unlock(radio->core); + + return err; +} +#endif + +static int si476x_radio_fops_open(struct file *file) +{ + struct si476x_radio *radio = video_drvdata(file); + int err; + + err = v4l2_fh_open(file); + if (err) + return err; + + if (v4l2_fh_is_singular_file(file)) { + si476x_core_lock(radio->core); + err = si476x_core_set_power_state(radio->core, + SI476X_POWER_UP_FULL); + if (err < 0) + goto done; + + err = si476x_radio_do_post_powerup_init(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + err = si476x_radio_pretune(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + si476x_core_unlock(radio->core); + /*Must be done after si476x_core_unlock to prevent a deadlock*/ + v4l2_ctrl_handler_setup(&radio->ctrl_handler); + } + + return err; + +power_down: + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); +done: + si476x_core_unlock(radio->core); + v4l2_fh_release(file); + + return err; +} + +static int si476x_radio_fops_release(struct file *file) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (v4l2_fh_is_singular_file(file) && + atomic_read(&radio->core->is_alive)) + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); + + err = v4l2_fh_release(file); + + return err; +} + +static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rval; + size_t fifo_len; + unsigned int copied; + + struct si476x_radio *radio = video_drvdata(file); + + /* block if no new data available */ + if (kfifo_is_empty(&radio->core->rds_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + rval = wait_event_interruptible(radio->core->rds_read_queue, + (!kfifo_is_empty(&radio->core->rds_fifo) || + !atomic_read(&radio->core->is_alive))); + if (rval < 0) + return -EINTR; + + if (!atomic_read(&radio->core->is_alive)) + return -ENODEV; + } + + fifo_len = kfifo_len(&radio->core->rds_fifo); + + if (kfifo_to_user(&radio->core->rds_fifo, buf, + min(fifo_len, count), + &copied) != 0) { + dev_warn(&radio->videodev.dev, + "Error during FIFO to userspace copy\n"); + rval = -EIO; + } else { + rval = (ssize_t)copied; + } + + return rval; +} + +static unsigned int si476x_radio_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct si476x_radio *radio = video_drvdata(file); + unsigned long req_events = poll_requested_events(pts); + unsigned int err = v4l2_ctrl_poll(file, pts); + + if (req_events & (POLLIN | POLLRDNORM)) { + if (atomic_read(&radio->core->is_alive)) + poll_wait(file, &radio->core->rds_read_queue, pts); + + if (!atomic_read(&radio->core->is_alive)) + err = POLLHUP; + + if (!kfifo_is_empty(&radio->core->rds_fifo)) + err = POLLIN | POLLRDNORM; + } + + return err; +} + +static const struct v4l2_file_operations si476x_fops = { + .owner = THIS_MODULE, + .read = si476x_radio_fops_read, + .poll = si476x_radio_fops_poll, + .unlocked_ioctl = video_ioctl2, + .open = si476x_radio_fops_open, + .release = si476x_radio_fops_release, +}; + + +static const struct v4l2_ioctl_ops si4761_ioctl_ops = { + .vidioc_querycap = si476x_radio_querycap, + .vidioc_g_tuner = si476x_radio_g_tuner, + .vidioc_s_tuner = si476x_radio_s_tuner, + + .vidioc_g_frequency = si476x_radio_g_frequency, + .vidioc_s_frequency = si476x_radio_s_frequency, + .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_g_chip_ident = si476x_radio_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = si476x_radio_g_register, + .vidioc_s_register = si476x_radio_s_register, +#endif +}; + + +static const struct video_device si476x_viddev_template = { + .fops = &si476x_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, +}; + + + +static ssize_t si476x_radio_read_acf_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_acf_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->acf_status) + err = radio->ops->acf_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_acf_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_acf_blob, +}; + +static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rds_blockcount_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rds_blckcnt(radio->core, true, + &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rds_blckcnt_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rds_blckcnt_blob, +}; + +static ssize_t si476x_radio_read_agc_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_agc_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->agc_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_agc_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_agc_blob, +}; + +static ssize_t si476x_radio_read_rsq_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_blob, +}; + +static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = true, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_primary_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_primary_blob, +}; + + +static int si476x_radio_init_debugfs(struct si476x_radio *radio) +{ + struct dentry *dentry; + int ret; + + dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto exit; + } + radio->debugfs = dentry; + + dentry = debugfs_create_file("acf", S_IRUGO, + radio->debugfs, radio, &radio_acf_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, + radio->debugfs, radio, + &radio_rds_blckcnt_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("agc", S_IRUGO, + radio->debugfs, radio, &radio_agc_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq", S_IRUGO, + radio->debugfs, radio, &radio_rsq_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq_primary", S_IRUGO, + radio->debugfs, radio, + &radio_rsq_primary_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + return 0; +cleanup: + debugfs_remove_recursive(radio->debugfs); +exit: + return ret; +} + + +static int si476x_radio_add_new_custom(struct si476x_radio *radio, + enum si476x_ctrl_idx idx) +{ + int rval; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, + &si476x_ctrls[idx], + NULL); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) + dev_err(radio->v4l2dev.dev, + "Could not initialize '%s' control %d\n", + si476x_ctrls[idx].name, rval); + + return rval; +} + +static int si476x_radio_probe(struct platform_device *pdev) +{ + int rval; + struct si476x_radio *radio; + struct v4l2_ctrl *ctrl; + + static atomic_t instance = ATOMIC_INIT(0); + + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->core = i2c_mfd_cell_to_core(&pdev->dev); + + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); + + rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); + if (rval) { + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); + return rval; + } + + memcpy(&radio->videodev, &si476x_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = &radio->v4l2dev; + radio->videodev.ioctl_ops = &si4761_ioctl_ops; + + video_set_drvdata(&radio->videodev, radio); + platform_set_drvdata(pdev, radio); + + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); + + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; + v4l2_ctrl_handler_init(&radio->ctrl_handler, + 1 + ARRAY_SIZE(si476x_ctrls)); + + if (si476x_core_has_am(radio->core)) { + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", + rval); + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, + SI476X_IDX_HARMONICS_COUNT); + if (rval < 0) + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); + if (rval < 0) + goto exit; + + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_TUNE_DEEMPHASIS, + V4L2_DEEMPHASIS_75_uS, 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", + rval); + goto exit; + } + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_RDS_RECEPTION, + 0, 1, 1, 1); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", + rval); + goto exit; + } + + if (si476x_core_has_diversity(radio->core)) { + si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = + si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); + si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); + if (rval < 0) + goto exit; + + si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); + if (rval < 0) + goto exit; + } + + /* register video device */ + rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); + if (rval < 0) { + dev_err(&pdev->dev, "Could not register video device\n"); + goto exit; + } + + rval = si476x_radio_init_debugfs(radio); + if (rval < 0) { + dev_err(&pdev->dev, "Could not creat debugfs interface\n"); + goto exit; + } + + return 0; +exit: + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + return rval; +} + +static int si476x_radio_remove(struct platform_device *pdev) +{ + struct si476x_radio *radio = platform_get_drvdata(pdev); + + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2dev); + debugfs_remove_recursive(radio->debugfs); + + return 0; +} + +MODULE_ALIAS("platform:si476x-radio"); + +static struct platform_driver si476x_radio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = si476x_radio_probe, + .remove = si476x_radio_remove, +}; +module_platform_driver(si476x_radio_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); +MODULE_LICENSE("GPL"); -- cgit v1.1