Philips tea5761 radio Linux driver for N800

Source: Internet
Author: User
Tags 04x
AM/FM Radio driver patch for N800. Compile Tested Only. Please test if
one has access to N800.
From: Trilok Soni <soni.trilok at gmail.com>
Date: Fri, 16 Feb 2007 00:09:08 +0530
Subject: [PATCH] : OMAP: Philips TEA5761 Radio driver for N800

- Taken from maemo.org N800 kernel package.

Signed-off-by: Trilok Soni <soni.trilok at gmail.com>
---
drivers/media/radio/Kconfig | 15 +
drivers/media/radio/Makefile | 1 +
drivers/media/radio/radio-tea5761.c | 636 +++++++++++++++++++++++++++++++++++
3 files changed, 652 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/radio/radio-tea5761.c
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfigindex 920b63f..45228b2 100644--- a/drivers/media/radio/Kconfig+++ b/drivers/media/radio/Kconfig@@ -317,6 +317,21 @@ config RADIO_ZOLTRIX_PORT help   Enter the I/O port of your Zoltrix radio card. +config RADIO_TEA5761+tristate "Philips Semiconductors TEA5761 I2C FM Radio"+select I2C+select VIDEO_V4L2+help+  Choose Y here if you have one of these AM/FM radio cards.++  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 "v4l" programs may be found at+  <file:Documentation/video4linux/API.html>.++  To compile this driver as a module, choose M here: the+  module will be called radio-tea5761.+ config USB_DSBR tristate "D-Link USB FM radio support (EXPERIMENTAL)" depends on USB && VIDEO_V4L2 && EXPERIMENTALdiff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefileindex cf55a18..f3555fa 100644--- a/drivers/media/radio/Makefile+++ b/drivers/media/radio/Makefile@@ -20,6 +20,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o obj-$(CONFIG_RADIO_GEMTEK_PCI) += radio-gemtek-pci.o obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o+obj-$(CONFIG_RADIO_TEA5761) += radio-tea5761.o obj-$(CONFIG_USB_DSBR) += dsbr100.o  EXTRA_CFLAGS += -Isounddiff --git a/drivers/media/radio/radio-tea5761.c b/drivers/media/radio/radio-tea5761.cnew file mode 100644index 0000000..74fe99e--- /dev/null+++ b/drivers/media/radio/radio-tea5761.c@@ -0,0 +1,636 @@+/*+ * drivers/media/radio/radio-tea5761.c+ *+ * Copyright (C) 2005 Nokia Corporation+ *+ * 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; either version 2 of the License, or+ * (at your option) any later version.+ *+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA+ */+#include <linux/module.h>+#include <linux/version.h>+#include <linux/init.h>+#include <linux/i2c.h>+#include <linux/delay.h>+#include <media/v4l2-common.h>+#include <asm/arch/gpio.h>+#include <asm/arch/board.h>++#define DRIVER_NAME "tea5761"++#define TEA5761_VERSIONKERNEL_VERSION(0, 0, 1)++#define TEA5761_I2C_ADDR0x10++#define TEA5761_MANID0x002b+#define TEA5761_CHIPID0x5761++#define TEA5761_INTREG_BLMSK0x0001+#define TEA5761_INTREG_FRRMSK0x0002+#define TEA5761_INTREG_LEVMSK0x0008+#define TEA5761_INTREG_IFMSK0x0010+#define TEA5761_INTREG_BLMFLAG0x0100+#define TEA5761_INTREG_FRRFLAG0x0200+#define TEA5761_INTREG_LEVFLAG0x0800+#define TEA5761_INTREG_IFFLAG0x1000++#define TEA5761_FRQSET_SUD0x8000+#define TEA5761_FRQSET_SM0x4000++#define TEA5761_TNCTRL_PUPD00x4000+#define TEA5761_TNCTRL_BLIM0x2000+#define TEA5761_TNCTRL_SWPM0x1000+#define TEA5761_TNCTRL_IFCTC0x0800+#define TEA5761_TNCTRL_AFM0x0400+#define TEA5761_TNCTRL_SMUTE0x0200+#define TEA5761_TNCTRL_SNC0x0100+#define TEA5761_TNCTRL_MU0x0080+#define TEA5761_TNCTRL_SSL10x0040+#define TEA5761_TNCTRL_SSL00x0020+#define TEA5761_TNCTRL_HLSI0x0010+#define TEA5761_TNCTRL_MST0x0008+#define TEA5761_TNCTRL_SWP0x0004+#define TEA5761_TNCTRL_DTC0x0002+#define TEA5761_TNCTRL_AHLSI0x0001++#define TEA5761_TUNCHK_LEVEL(x)(((x) & 0x00F0) >> 4)+#define TEA5761_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9)+#define TEA5761_TUNCHK_TUNTO0x0100+#define TEA5761_TUNCHK_LD0x0008+#define TEA5761_TUNCHK_STEREO0x0004++#define TEA5761_TESTREG_TRIGFR0x0800++#define TEA5761_FREQ_LOW87500+#define TEA5761_FREQ_HIGH108000++/* Probe for TEA5761 twice since the version N4B seems to be+ * broken and needs two probes to be found */+static unsigned short normal_i2c[] = {+TEA5761_I2C_ADDR, TEA5761_I2C_ADDR, I2C_CLIENT_END+};++I2C_CLIENT_INSMOD;++struct tea5761_regs {+u16 intreg;+u16 frqset;+u16 tnctrl;+u16 frqchk;+u16 tunchk;+u16 testreg;+u16 manid;+u16 chipid;+} __attribute__ ((packed));++struct tea5761_write_regs {+u8 intreg;+u16 frqset;+u16 tnctrl;+u16 testreg;+} __attribute__ ((packed));++struct tea5761_device {+struct video_device*video_dev;+struct i2c_client*i2c_dev;+struct tea5761_regsregs;+struct mutexmutex;+intusers;+};++static struct tea5761_device tea5761;++static struct i2c_drivertea5761_driver;+static int radio_nr = -1;++static int tea5761_read_regs(struct tea5761_device *tea)+{+int rc, i;+u16 *p = (u16 *) &tea->regs;+struct i2c_client *client = tea->i2c_dev;++rc = i2c_master_recv(client, (void*) &tea->regs, sizeof(tea->regs));+for (i = 0; i < 8; i++) {+p[i] = __be16_to_cpu(p[i]);+}++dev_dbg(&client->dev,+"chip state: %04x %04x %04x %04x %04x %04x %04x %04x/n",+p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);++if (rc < 0)+dev_err(&client->dev, "read/n");++return rc;+}++static void tea5761_write_regs(struct tea5761_device *tea)+{+struct tea5761_write_regs wr;+struct tea5761_regs *r = &tea->regs;+struct i2c_client *client = tea->i2c_dev;+#ifdef DEBUG+u8 *p = (u8 *) r;+#endif++wr.intreg = r->intreg & 0xff;+wr.frqset = __cpu_to_be16(r->frqset);+wr.tnctrl = __cpu_to_be16(r->tnctrl);+wr.testreg = __cpu_to_be16(r->testreg);++dev_dbg(&client->dev,+"writing state: %02x %02x %02x %02x %02x %02x %02x/n",+p[0], p[1], p[2], p[3], p[4], p[5], p[6]);+if (i2c_master_send(client, (void *) &wr, sizeof(wr)) < 0)+dev_err(&client->dev, "write/n");+}++static void tea5761_power_up(struct tea5761_device *tea)+{+struct tea5761_regs *r = &tea->regs;++if (!(r->tnctrl & TEA5761_TNCTRL_PUPD0)) {+r->tnctrl &= ~(TEA5761_TNCTRL_AFM | TEA5761_TNCTRL_MU |+       TEA5761_TNCTRL_HLSI);+r->testreg |= TEA5761_TESTREG_TRIGFR;+r->tnctrl |= TEA5761_TNCTRL_PUPD0;+return tea5761_write_regs(tea);+}+}++static void tea5761_power_down(struct tea5761_device *tea)+{+struct tea5761_regs *r = &tea->regs;++if (r->tnctrl & TEA5761_TNCTRL_PUPD0) {+r->tnctrl &= ~TEA5761_TNCTRL_PUPD0;+return tea5761_write_regs(tea);+}+}++static void tea5761_set_freq(struct tea5761_device *tea, int freq)+{+struct tea5761_regs *r = &tea->regs;++if (r->tnctrl & TEA5761_TNCTRL_HLSI)+r->frqset = (freq + 225000) / 8192;+else+r->frqset = (freq - 225000) / 8192;+}++static int tea5761_get_freq(struct tea5761_device *tea)+{+struct tea5761_regs *r = &tea->regs;++if (r->tnctrl & TEA5761_TNCTRL_HLSI)+return (r->frqchk * 8192) - 225000;+else+return (r->frqchk * 8192) + 225000;+}++static void tea5761_tune(struct tea5761_device *tea, int freq)+{+tea5761_set_freq(tea, freq);+tea5761_write_regs(tea);+}++static void tea5761_set_audout_mode(struct tea5761_device *tea, int audmode)+{+struct tea5761_regs *r = &tea->regs;+int tnctrl = r->tnctrl;++if (audmode == V4L2_TUNER_MODE_MONO)+r->tnctrl |= TEA5761_TNCTRL_MST;+else+r->tnctrl &= ~TEA5761_TNCTRL_MST;+if (tnctrl != r->tnctrl)+tea5761_write_regs(tea);+}++static int tea5761_get_audout_mode(struct tea5761_device *tea)+{+struct tea5761_regs *r = &tea->regs;++if (r->tnctrl & TEA5761_TNCTRL_MST)+return V4L2_TUNER_MODE_MONO;+else+return V4L2_TUNER_MODE_STEREO;+}++static void tea5761_mute(struct tea5761_device *tea, int on)+{+struct tea5761_regs *r = &tea->regs;+int tnctrl = r->tnctrl;++if (on)+r->tnctrl |= TEA5761_TNCTRL_MU;+else+r->tnctrl &= ~TEA5761_TNCTRL_MU;+if (tnctrl != r->tnctrl)+tea5761_write_regs(tea);+}++static int tea5761_is_muted(struct tea5761_device *tea)+{+return tea->regs.tnctrl & TEA5761_TNCTRL_MU;+}++static int tea5761_do_ioctl(struct inode *inode, struct file *file,+    unsigned int cmd, void *arg)+{+struct tea5761_device *tea = file->private_data;+struct video_device *dev = tea->video_dev;+struct i2c_client *client = tea->i2c_dev;+struct tea5761_regs *r = &tea->regs;++union {+struct v4l2_capability c;+struct v4l2_tuner t;+struct v4l2_frequency f;+struct v4l2_queryctrl qc;+struct v4l2_control ct;+} *u = arg;++tea5761_read_regs(tea);++switch (cmd) {+case VIDIOC_QUERYCAP:+dev_dbg(&client->dev, "VIDIOC_QUERYCAP/n");+memset(&u->c, 0, sizeof(u->c));+strlcpy(u->c.driver, dev->dev->driver->name,+sizeof(u->c.driver));+strlcpy(u->c.card, dev->name, sizeof(u->c.card));+snprintf(u->c.bus_info, sizeof(u->c.bus_info), "I2C:%s",+ dev->dev->bus_id);+u->c.version = TEA5761_VERSION;+u->c.capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;+break;++case VIDIOC_G_TUNER:+/* Only one tuner chip */+dev_dbg(&client->dev, "VIDIOC_G_TUNER/n");+if (u->t.index != 0)+return -EINVAL;++memset(&u->t, 0, sizeof(u->t));+u->t.type = V4L2_TUNER_RADIO;+strlcpy(u->t.name, "FM", sizeof(u->t.name));+/* Freq in 62.5Hz units */+u->t.rangelow = TEA5761_FREQ_LOW * 16;+u->t.rangehigh = TEA5761_FREQ_HIGH * 16;+u->t.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;+if (r->tunchk & TEA5761_TUNCHK_STEREO)+u->t.rxsubchans = V4L2_TUNER_SUB_STEREO;+u->t.audmode = tea5761_get_audout_mode(tea);+u->t.signal = TEA5761_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf;+u->t.afc = TEA5761_TUNCHK_IFCNT(r->tunchk);+break;++case VIDIOC_S_TUNER:+/* Only tuner nro 0 can be selected. */+dev_dbg(&client->dev, "VIDIOC_S_TUNER/n");+if (u->t.index != 0)+return -EINVAL;+tea5761_set_audout_mode(tea, u->t.audmode);+break;++case VIDIOC_G_FREQUENCY:+dev_dbg(&client->dev, "VIDIOC_G_FREQUENCY/n");+memset(&u->f, 0, sizeof(u->f));+u->f.type = V4L2_TUNER_RADIO;+if (r->tnctrl & TEA5761_TNCTRL_PUPD0)+u->f.frequency = (tea5761_get_freq(tea) * 2) / 125;+else+u->f.frequency = 0;+break;++case VIDIOC_S_FREQUENCY:+dev_dbg(&client->dev, "VIDIOC_S_FREQUENCY %u/n",+u->f.frequency);+if (u->f.tuner != 0)+return -EINVAL;+if (u->f.frequency == 0) {+/* We special case this as a power down+ * control. */+tea5761_power_down(tea);+break;+}+if (u->f.frequency < 16 * TEA5761_FREQ_LOW)+return -EINVAL;+if (u->f.frequency > 16 * TEA5761_FREQ_HIGH)+return -EINVAL;++tea5761_power_up(tea);+tea5761_tune(tea, (u->f.frequency * 125) / 2);+break;++case VIDIOC_QUERYCTRL:+dev_dbg(&client->dev, "VIDIOC_QUERYCTRL %d/n", u->qc.id);+if (u->qc.id != V4L2_CID_AUDIO_MUTE)+return -EINVAL;+strlcpy(u->qc.name, "Mute", sizeof(u->qc.name));+u->qc.minimum = 0;+u->qc.maximum = 1;+u->qc.step = 1;+u->qc.default_value = 0;+u->qc.type = V4L2_CTRL_TYPE_BOOLEAN;+break;++case VIDIOC_G_CTRL:+dev_dbg(&client->dev, "VIDIOC_G_CTRL %d/n", u->ct.id);+if (u->ct.id != V4L2_CID_AUDIO_MUTE)+return -EINVAL;+if (r->tnctrl & TEA5761_TNCTRL_PUPD0)+u->ct.value = tea5761_is_muted(tea) ? 1 : 0;+else+u->ct.value = 0;+break;++case VIDIOC_S_CTRL:+dev_dbg(&client->dev, "VIDIOC_S_CTRL %d/n", u->ct.id);+if (u->ct.id != V4L2_CID_AUDIO_MUTE)+return -EINVAL;+tea5761_mute(tea, u->ct.value);+break;++default:+return -ENOIOCTLCMD;+}++return 0;+}++static int tea5761_ioctl(struct inode *inode, struct file *file,+ unsigned int cmd, unsigned long arg)+{+return video_usercopy(inode, file, cmd, arg, tea5761_do_ioctl);+}++static int tea5761_open(struct inode *inode, struct file *file)+{+int minor = iminor(file->f_dentry->d_inode);+/* Currently we support only one device */+struct tea5761_device *tea = &tea5761;++if (tea->video_dev->minor != minor)+return -ENODEV;++mutex_lock(&tea->mutex);+/* Only exclusive access */+if (tea->users) {+mutex_unlock(&tea->mutex);+return -EBUSY;+}+tea->users++;+mutex_unlock(&tea->mutex);++file->private_data = tea;+return 0;+}++static int tea5761_release(struct inode *inode, struct file *file)+{+struct tea5761_device *tea = file->private_data;++mutex_lock(&tea->mutex);+tea->users--;+mutex_unlock(&tea->mutex);++return 0;+}++static struct file_operations tea5761_fops = {+.owner= THIS_MODULE,+.open           = tea5761_open,+.release= tea5761_release,+.ioctl= tea5761_ioctl,+.llseek         = no_llseek,+};++static struct video_device tea5761_video_device = {+.owner         = THIS_MODULE,+.name          = "TEA5761 FM-Radio",+.type          = VID_TYPE_TUNER,+.hardware      = 40 /* VID_HARDWARE_TEA5761UK */,+.fops          = &tea5761_fops,+.release       = video_device_release+};++static int tea5761_probe(struct i2c_adapter *adapter, int address,+  int kind)+{+struct i2c_client *client;+struct video_device *video_dev;+int err = 0;+static const char *client_name = "TEA5761 FM-Radio";+struct tea5761_device *tea = &tea5761;+struct tea5761_regs   *r = &tea->regs;++mutex_init(&tea->mutex);+        /* I2C detection and initialization */+client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);+if (client == NULL) {+dev_err(&adapter->dev, DRIVER_NAME+": couldn't allocate memory/n");+return -ENOMEM;+}+tea->i2c_dev = client;++client->addr = address;+client->adapter = adapter;+client->driver = &tea5761_driver;+client->dev.driver = &tea5761_driver.driver;+client->flags = 0;+strlcpy(client->name, client_name, I2C_NAME_SIZE);++if (kind < 0) {+if (tea5761_read_regs(tea) < 0) {+dev_info(&client->dev,+ "chip read failed for %d-%04x/n",+ adapter->nr, address);+goto err_tea_dev;+}+if (r->chipid != TEA5761_CHIPID) {+dev_info(&client->dev,+ "bad chipid (0x%04x) at %d-%04x/n",+ r->chipid, adapter->nr, address);+goto err_tea_dev;+}+if ((r->manid & 0x0fff) != TEA5761_MANID) {+dev_info(&client->dev,+ "bad manid (0x%04x) at %d-%04x/n",+ r->manid, adapter->nr, address);+goto err_tea_dev;+}+}++err = i2c_attach_client(client);+if (err) {+dev_err(&client->dev, "couldn't attach to address %d-%04x/n",+        adapter->nr, address);+goto err_tea_dev;+}++/* V4L initialization */+video_dev = video_device_alloc();+if (video_dev == NULL) {+dev_err(&client->dev, "couldn't allocate memory/n");+err = -ENOMEM;+goto err_i2c_attach;+}+tea->video_dev = video_dev;++*video_dev = tea5761_video_device;+video_dev->dev = &client->dev;+i2c_set_clientdata(client, video_dev);++/* initialize and power off the chip */+tea5761_read_regs(tea);+tea5761_set_audout_mode(tea, V4L2_TUNER_MODE_STEREO);+tea5761_mute(tea, 0);+tea5761_power_down(tea);++tea5761.video_dev = video_dev;+tea5761.i2c_dev = client;++err = video_register_device(video_dev, VFL_TYPE_RADIO, radio_nr);+if (err) {+dev_err(&client->dev, "couldn't register video device/n");+goto err_video_alloc;+}++dev_info(&client->dev, "tea5761 (version %d) detected at %d-%04x/n",+(tea->regs.manid >> 12) & 0xf, adapter->nr, address);++return 0;++err_video_alloc:+video_device_release(video_dev);+err_i2c_attach:+i2c_detach_client(client);+err_tea_dev:+kfree(client);+return err;+}++static int tea5761_attach_adapter(struct i2c_adapter *adapter)+{+if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))+return -EINVAL;++return i2c_probe(adapter, &addr_data, tea5761_probe);+}++static int tea5761_detach_client(struct i2c_client *client)+{+struct video_device *vd = i2c_get_clientdata(client);++i2c_detach_client(client);+video_unregister_device(vd);+kfree(client);++return 0;+}++static struct i2c_driver tea5761_driver = {+.id= I2C_DRIVERID_TUNER,+.driver = {+.name= DRIVER_NAME,+},+.attach_adapter= tea5761_attach_adapter,+.detach_client= tea5761_detach_client,+};++#if CONFIG_ARCH_OMAP+/* No way to pass platform device data. Enable here all the TEA5761+ * devices, since I2C address scanning will need them to respond.+ */+static int enable_gpio;++static int __init tea5761_dev_init(void)+{+const struct omap_tea5761_config *info;++info = omap_get_config(OMAP_TAG_TEA5761, struct omap_tea5761_config);+if (info) {+enable_gpio = info->enable_gpio;+}++if (enable_gpio) {+pr_debug(DRIVER_NAME ": enabling tea5761 at GPIO %d/n",+ enable_gpio);++if (omap_request_gpio(enable_gpio) < 0) {+printk(KERN_ERR DRIVER_NAME ": can't request GPIO %d/n",+       enable_gpio);+return -ENODEV;+}++omap_set_gpio_direction(enable_gpio, 0);+udelay(50);+omap_set_gpio_dataout(enable_gpio, 1);+}++return 0;+}++static void __exit tea5761_dev_exit(void)+{+if (enable_gpio) {+omap_set_gpio_dataout(enable_gpio, 0);+omap_free_gpio(enable_gpio);+}+}+#else+static int __init tea5761_dev_init(void)+{+}++static void __exit tea5761_dev_exit(void)+{+}+#endif++static int __init tea5761_init(void)+{+int res;++if ((res = tea5761_dev_init()) < 0)+return res;++if ((res = i2c_add_driver(&tea5761_driver))) {+printk(KERN_ERR DRIVER_NAME ": driver registration failed/n");+return res;+}++return 0;+}++static void __exit tea5761_exit(void)+{+int res;++if ((res = i2c_del_driver(&tea5761_driver)))+printk(KERN_ERR DRIVER_NAME ": i2c driver removal failed/n");+tea5761_dev_exit();+}++MODULE_AUTHOR("Timo Ter?s");+MODULE_DESCRIPTION("I2C interface for TEA5761.");+MODULE_LICENSE("GPL");++module_param(radio_nr, int, 0);+MODULE_PARM_DESC(nr_radio, "video4linux device number to use");++module_init(tea5761_init)+module_exit(tea5761_exit)
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.