From: "Richard Röjfors" <richard.rojfors@pelagicore.com>
To: Linux Media Mailing List <linux-media@vger.kernel.org>
Cc: Mauro Carvalho Chehab <mchehab@infradead.org>,
Douglas Schilling Landgraf <dougsland@gmail.com>,
Andrew Morton <akpm@linux-foundation.org>
Subject: [PATCH 1/2] radio: Add radio-timb
Date: Fri, 22 Jan 2010 13:38:28 +0100 [thread overview]
Message-ID: <4B599C44.4030801@pelagicore.com> (raw)
This patch add supports for the radio system on the Intel Russellville board.
It's a In-Vehicle Infotainment board with a radio tuner and DSP.
This umbrella driver has the DSP and tuner as V4L2 subdevs and calls them
when needed.
The RDS support is done by I/O memory accesses.
Signed-off-by: Richard Röjfors <richard.rojfors@pelagicore.com>
---
diff --git a/drivers/media/radio/radio-timb.c b/drivers/media/radio/radio-timb.c
new file mode 100644
index 0000000..3dbe9ad
--- /dev/null
+++ b/drivers/media/radio/radio-timb.c
@@ -0,0 +1,543 @@
+/*
+ * radio-timb.c Timberdale FPGA Radio driver
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <media/timb_radio.h>
+
+#define DRIVER_NAME "timb-radio"
+
+#define RDS_BLOCK_SIZE 4
+#define RDS_BUFFER_SIZE (RDS_BLOCK_SIZE * 100)
+
+struct timbradio {
+ struct mutex lock; /* for mutual exclusion */
+ void __iomem *membase;
+ struct timb_radio_platform_data pdata;
+ struct v4l2_subdev *sd_tuner;
+ struct module *tuner_owner;
+ struct v4l2_subdev *sd_dsp;
+ struct module *dsp_owner;
+ struct video_device *video_dev;
+ /* RDS related */
+ int open_count;
+ int rds_irq;
+ wait_queue_head_t read_queue;
+ unsigned char buffer[RDS_BUFFER_SIZE];
+ unsigned int rd_index;
+ unsigned int wr_index;
+};
+
+
+static int timbradio_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, DRIVER_NAME, sizeof(v->driver));
+ strlcpy(v->card, "Timberdale Radio", sizeof(v->card));
+ snprintf(v->bus_info, sizeof(v->bus_info), "platform:"DRIVER_NAME);
+ v->version = KERNEL_VERSION(0, 0, 1);
+ v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ return 0;
+}
+
+static int timbradio_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_tuner, tuner, g_tuner, v);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static int timbradio_vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_tuner, tuner, s_tuner, v);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static int timbradio_vidioc_g_input(struct file *filp, void *priv,
+ unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int timbradio_vidioc_s_input(struct file *filp, void *priv,
+ unsigned int i)
+{
+ return i ? -EINVAL : 0;
+}
+
+static int timbradio_vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ a->index = 0;
+ strlcpy(a->name, "Radio", sizeof(a->name));
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+
+static int timbradio_vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ return a->index ? -EINVAL : 0;
+}
+
+static int timbradio_vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_tuner, tuner, s_frequency, f);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static int timbradio_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_tuner, tuner, g_frequency, f);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static int timbradio_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_dsp, core, queryctrl, qc);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static int timbradio_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_dsp, core, g_ctrl, ctrl);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static int timbradio_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&tr->lock);
+ ret = v4l2_subdev_call(tr->sd_dsp, core, s_ctrl, ctrl);
+ mutex_unlock(&tr->lock);
+
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops timbradio_ioctl_ops = {
+ .vidioc_querycap = timbradio_vidioc_querycap,
+ .vidioc_g_tuner = timbradio_vidioc_g_tuner,
+ .vidioc_s_tuner = timbradio_vidioc_s_tuner,
+ .vidioc_g_frequency = timbradio_vidioc_g_frequency,
+ .vidioc_s_frequency = timbradio_vidioc_s_frequency,
+ .vidioc_g_input = timbradio_vidioc_g_input,
+ .vidioc_s_input = timbradio_vidioc_s_input,
+ .vidioc_g_audio = timbradio_vidioc_g_audio,
+ .vidioc_s_audio = timbradio_vidioc_s_audio,
+ .vidioc_queryctrl = timbradio_vidioc_queryctrl,
+ .vidioc_g_ctrl = timbradio_vidioc_g_ctrl,
+ .vidioc_s_ctrl = timbradio_vidioc_s_ctrl
+};
+
+static irqreturn_t timbradio_irq(int irq, void *devid)
+{
+ struct timbradio *tr = devid;
+ u32 data = ioread32(tr->membase);
+
+ tr->buffer[tr->wr_index++] = data >> 24;
+ tr->buffer[tr->wr_index++] = data >> 16;
+ tr->buffer[tr->wr_index++] = data >> 8;
+ tr->buffer[tr->wr_index++] = data;
+ tr->wr_index %= RDS_BUFFER_SIZE;
+
+ wake_up(&tr->read_queue);
+
+ /* new RDS data received, read it */
+ return IRQ_HANDLED;
+}
+
+/**************************************************************************
+ * File Operations Interface
+ **************************************************************************/
+
+static ssize_t timbradio_rds_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int outblocks = 0;
+
+ /* block if no new data available */
+ while (tr->wr_index == tr->rd_index) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ if (wait_event_interruptible(tr->read_queue,
+ tr->wr_index != tr->rd_index))
+ return -EINTR;
+ }
+
+ count /= RDS_BLOCK_SIZE;
+ /* copy RDS block out of internal buffer and to user buffer */
+ mutex_lock(&tr->lock);
+ while (outblocks < count) {
+ if (tr->rd_index == tr->wr_index)
+ break;
+
+ if (copy_to_user(buf, tr->buffer + tr->rd_index,
+ RDS_BLOCK_SIZE))
+ break;
+ tr->rd_index += RDS_BLOCK_SIZE;
+ tr->rd_index %= RDS_BUFFER_SIZE;
+ outblocks++;
+ }
+ mutex_unlock(&tr->lock);
+
+ return outblocks * RDS_BLOCK_SIZE;
+}
+
+static unsigned int timbradio_rds_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct timbradio *tr = video_drvdata(file);
+
+ poll_wait(file, &tr->read_queue, pts);
+
+ if (tr->rd_index != tr->wr_index)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+struct find_addr_arg {
+ char const *name;
+ struct i2c_client *client;
+};
+
+static int find_name(struct device *dev, void *argp)
+{
+ struct find_addr_arg *arg = (struct find_addr_arg *)argp;
+ struct i2c_client *client = i2c_verify_client(dev);
+
+ if (client && !strcmp(arg->name, client->name) && client->driver)
+ arg->client = client;
+
+ return 0;
+}
+
+static struct i2c_client *find_client(struct i2c_adapter *adapt,
+ const char *name)
+{
+ struct find_addr_arg find_arg;
+ /* now find the client */
+#ifdef MODULE
+ request_module(name);
+#endif
+ /* code for finding the I2C child */
+ find_arg.name = name;
+ find_arg.client = NULL;
+ device_for_each_child(&adapt->dev, &find_arg, find_name);
+ return find_arg.client;
+}
+
+static int timbradio_rds_fops_open(struct file *file)
+{
+ struct timbradio *tr = video_drvdata(file);
+ int err = 0;
+
+ mutex_lock(&tr->lock);
+ if (tr->open_count == 0) {
+ /* device currently not open, check if the DSP and tuner is not
+ * yet found, in that case find them
+ */
+ if (!tr->sd_tuner) {
+ struct i2c_adapter *adapt;
+ struct i2c_client *tuner;
+ struct i2c_client *dsp;
+
+ /* find the I2C bus */
+ adapt = i2c_get_adapter(tr->pdata.i2c_adapter);
+ if (!adapt) {
+ printk(KERN_ERR DRIVER_NAME": No I2C bus\n");
+ err = -ENODEV;
+ goto out;
+ }
+
+ /* now find the tuner and dsp */
+ tuner = find_client(adapt, tr->pdata.tuner);
+ dsp = find_client(adapt, tr->pdata.dsp);
+
+ i2c_put_adapter(adapt);
+
+ if (!tuner || !dsp) {
+ printk(KERN_ERR DRIVER_NAME
+ ": Failed to get tuner or DSP\n");
+ err = -ENODEV;
+ goto out;
+ }
+
+ tr->sd_tuner = i2c_get_clientdata(tuner);
+ tr->sd_dsp = i2c_get_clientdata(dsp);
+
+ tr->tuner_owner = tr->sd_tuner->owner;
+ tr->dsp_owner = tr->sd_dsp->owner;
+ /* Lock the modules */
+ if (!try_module_get(tr->tuner_owner)) {
+ err = -ENODEV;
+ goto err_get_tuner;
+ }
+
+ if (!try_module_get(tr->dsp_owner)) {
+ err = -ENODEV;
+ goto err_get_dsp;
+ }
+ }
+
+ /* enable the IRQ for receiving RDS data */
+ err = request_irq(tr->rds_irq, timbradio_irq, 0, DRIVER_NAME,
+ tr);
+ }
+ goto out;
+
+err_get_dsp:
+ module_put(tr->tuner_owner);
+err_get_tuner:
+ tr->sd_tuner = NULL;
+ tr->sd_dsp = NULL;
+out:
+ if (!err)
+ tr->open_count++;
+ mutex_unlock(&tr->lock);
+ return err;
+}
+
+static int timbradio_rds_fops_release(struct file *file)
+{
+ struct timbradio *tr = video_drvdata(file);
+
+ mutex_lock(&tr->lock);
+ tr->open_count--;
+ if (!tr->open_count) {
+ free_irq(tr->rds_irq, tr);
+
+ tr->wr_index = 0;
+ tr->rd_index = 0;
+
+ /* cancel read processes */
+ wake_up_interruptible(&tr->read_queue);
+ }
+ mutex_unlock(&tr->lock);
+
+ return 0;
+}
+
+
+static const struct v4l2_file_operations timbradio_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = video_ioctl2,
+ .read = timbradio_rds_fops_read,
+ .poll = timbradio_rds_fops_poll,
+ .open = timbradio_rds_fops_open,
+ .release = timbradio_rds_fops_release,
+};
+
+static const struct video_device timbradio_template = {
+ .name = "Timberdale Radio",
+ .fops = &timbradio_fops,
+ .ioctl_ops = &timbradio_ioctl_ops,
+ .release = video_device_release_empty,
+ .minor = -1
+};
+
+
+
+static int timbradio_probe(struct platform_device *pdev)
+{
+ struct timb_radio_platform_data *pdata = pdev->dev.platform_data;
+ struct timbradio *tr;
+ struct resource *iomem;
+ int irq;
+ int err;
+
+ if (!pdata) {
+ printk(KERN_ERR DRIVER_NAME": Platform data missing\n");
+ err = -EINVAL;
+ goto err;
+ }
+
+ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iomem) {
+ err = -ENODEV;
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ err = -ENODEV;
+ goto err;
+ }
+
+ if (!request_mem_region(iomem->start, resource_size(iomem),
+ DRIVER_NAME)) {
+ err = -EBUSY;
+ goto err;
+ }
+
+ tr = kzalloc(sizeof(*tr), GFP_KERNEL);
+ if (!tr) {
+ err = -ENOMEM;
+ goto err_alloc;
+ }
+ mutex_init(&tr->lock);
+
+ tr->membase = ioremap(iomem->start, resource_size(iomem));
+ if (!tr->membase) {
+ err = -ENOMEM;
+ goto err_ioremap;
+ }
+
+ memcpy(&tr->pdata, pdata, sizeof(tr->pdata));
+
+ tr->video_dev = video_device_alloc();
+ if (!tr->video_dev) {
+ err = -ENOMEM;
+ goto err_video_req;
+ }
+ *tr->video_dev = timbradio_template;
+ tr->rds_irq = irq;
+ init_waitqueue_head(&tr->read_queue);
+
+ err = video_register_device(tr->video_dev, VFL_TYPE_RADIO, -1);
+ if (err) {
+ printk(KERN_ALERT DRIVER_NAME": Error reg video\n");
+ goto err_video_req;
+ }
+
+ video_set_drvdata(tr->video_dev, tr);
+
+ platform_set_drvdata(pdev, tr);
+ return 0;
+
+err_video_req:
+ if (tr->video_dev->minor != -1)
+ video_unregister_device(tr->video_dev);
+ else
+ video_device_release(tr->video_dev);
+ iounmap(tr->membase);
+err_ioremap:
+ kfree(tr);
+err_alloc:
+ release_mem_region(iomem->start, resource_size(iomem));
+err:
+ printk(KERN_ERR DRIVER_NAME ": Failed to register: %d\n", err);
+
+ return err;
+}
+
+static int timbradio_remove(struct platform_device *pdev)
+{
+ struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct timbradio *tr = platform_get_drvdata(pdev);
+
+ if (tr->video_dev->minor != -1)
+ video_unregister_device(tr->video_dev);
+ else
+ video_device_release(tr->video_dev);
+
+ if (tr->sd_tuner) {
+ module_put(tr->tuner_owner);
+ module_put(tr->dsp_owner);
+ }
+
+ iounmap(tr->membase);
+ release_mem_region(iomem->start, resource_size(iomem));
+ kfree(tr);
+
+ return 0;
+}
+
+static struct platform_driver timbradio_platform_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = timbradio_probe,
+ .remove = timbradio_remove,
+};
+
+/*--------------------------------------------------------------------------*/
+
+static int __init timbradio_init(void)
+{
+ return platform_driver_register(&timbradio_platform_driver);
+}
+
+static void __exit timbradio_exit(void)
+{
+ platform_driver_unregister(&timbradio_platform_driver);
+}
+
+module_init(timbradio_init);
+module_exit(timbradio_exit);
+
+MODULE_DESCRIPTION("Timberdale Radio driver");
+MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:"DRIVER_NAME);
diff --git a/include/media/timb_radio.h b/include/media/timb_radio.h
new file mode 100644
index 0000000..6bea1b9
--- /dev/null
+++ b/include/media/timb_radio.h
@@ -0,0 +1,30 @@
+/*
+ * timb_radio.h Platform struct for the Timberdale radio driver
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _TIMB_RADIO_
+#define _TIMB_RADIO_
+
+#include <linux/i2c.h>
+
+struct timb_radio_platform_data {
+ int i2c_adapter; /* I2C adapter where the tuner and dsp are attached */
+ char tuner[32];
+ char dsp[32];
+};
+
+#endif
next reply other threads:[~2010-01-22 12:38 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2010-01-22 12:38 Richard Röjfors [this message]
2010-01-22 16:26 ` [PATCH 1/2] radio: Add radio-timb Hans Verkuil
2010-01-22 19:36 ` Richard Röjfors
2010-01-27 11:41 ` Hans Verkuil
2010-01-27 16:04 ` Richard Röjfors
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4B599C44.4030801@pelagicore.com \
--to=richard.rojfors@pelagicore.com \
--cc=akpm@linux-foundation.org \
--cc=dougsland@gmail.com \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.