Path: blob/master/drivers/media/radio/radio-tea5764.c
15111 views
/*1* driver/media/radio/radio-tea5764.c2*3* Driver for TEA5764 radio chip for linux 2.6.4* This driver is for TEA5764 chip from NXP, used in EZX phones from Motorola.5* The I2C protocol is used for communicate with chip.6*7* Based in radio-tea5761.c Copyright (C) 2005 Nokia Corporation8*9* Copyright (c) 2008 Fabio Belavenuto <[email protected]>10*11* This program is free software; you can redistribute it and/or modify12* it under the terms of the GNU General Public License as published by13* the Free Software Foundation; either version 2 of the License, or14* (at your option) any later version.15*16* This program is distributed in the hope that it will be useful,17* but WITHOUT ANY WARRANTY; without even the implied warranty of18* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the19* GNU General Public License for more details.20*21* You should have received a copy of the GNU General Public License22* along with this program; if not, write to the Free Software23* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA24*25* History:26* 2008-12-06 Fabio Belavenuto <[email protected]>27* initial code28*29* TODO:30* add platform_data support for IRQs platform dependencies31* add RDS support32*/33#include <linux/kernel.h>34#include <linux/slab.h>35#include <linux/module.h>36#include <linux/init.h> /* Initdata */37#include <linux/videodev2.h> /* kernel radio structs */38#include <linux/i2c.h> /* I2C */39#include <media/v4l2-common.h>40#include <media/v4l2-ioctl.h>41#include <linux/version.h> /* for KERNEL_VERSION MACRO */4243#define DRIVER_VERSION "v0.01"44#define RADIO_VERSION KERNEL_VERSION(0, 0, 1)4546#define DRIVER_AUTHOR "Fabio Belavenuto <[email protected]>"47#define DRIVER_DESC "A driver for the TEA5764 radio chip for EZX Phones."4849#define PINFO(format, ...)\50printk(KERN_INFO KBUILD_MODNAME ": "\51DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)52#define PWARN(format, ...)\53printk(KERN_WARNING KBUILD_MODNAME ": "\54DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)55# define PDEBUG(format, ...)\56printk(KERN_DEBUG KBUILD_MODNAME ": "\57DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)5859/* Frequency limits in MHz -- these are European values. For Japanese60devices, that would be 76000 and 91000. */61#define FREQ_MIN 8750062#define FREQ_MAX 10800063#define FREQ_MUL 166465/* TEA5764 registers */66#define TEA5764_MANID 0x002b67#define TEA5764_CHIPID 0x57646869#define TEA5764_INTREG_BLMSK 0x000170#define TEA5764_INTREG_FRRMSK 0x000271#define TEA5764_INTREG_LEVMSK 0x000872#define TEA5764_INTREG_IFMSK 0x001073#define TEA5764_INTREG_BLMFLAG 0x010074#define TEA5764_INTREG_FRRFLAG 0x020075#define TEA5764_INTREG_LEVFLAG 0x080076#define TEA5764_INTREG_IFFLAG 0x10007778#define TEA5764_FRQSET_SUD 0x800079#define TEA5764_FRQSET_SM 0x40008081#define TEA5764_TNCTRL_PUPD1 0x800082#define TEA5764_TNCTRL_PUPD0 0x400083#define TEA5764_TNCTRL_BLIM 0x200084#define TEA5764_TNCTRL_SWPM 0x100085#define TEA5764_TNCTRL_IFCTC 0x080086#define TEA5764_TNCTRL_AFM 0x040087#define TEA5764_TNCTRL_SMUTE 0x020088#define TEA5764_TNCTRL_SNC 0x010089#define TEA5764_TNCTRL_MU 0x008090#define TEA5764_TNCTRL_SSL1 0x004091#define TEA5764_TNCTRL_SSL0 0x002092#define TEA5764_TNCTRL_HLSI 0x001093#define TEA5764_TNCTRL_MST 0x000894#define TEA5764_TNCTRL_SWP 0x000495#define TEA5764_TNCTRL_DTC 0x000296#define TEA5764_TNCTRL_AHLSI 0x00019798#define TEA5764_TUNCHK_LEVEL(x) (((x) & 0x00F0) >> 4)99#define TEA5764_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9)100#define TEA5764_TUNCHK_TUNTO 0x0100101#define TEA5764_TUNCHK_LD 0x0008102#define TEA5764_TUNCHK_STEREO 0x0004103104#define TEA5764_TESTREG_TRIGFR 0x0800105106struct tea5764_regs {107u16 intreg; /* INTFLAG & INTMSK */108u16 frqset; /* FRQSETMSB & FRQSETLSB */109u16 tnctrl; /* TNCTRL1 & TNCTRL2 */110u16 frqchk; /* FRQCHKMSB & FRQCHKLSB */111u16 tunchk; /* IFCHK & LEVCHK */112u16 testreg; /* TESTBITS & TESTMODE */113u16 rdsstat; /* RDSSTAT1 & RDSSTAT2 */114u16 rdslb; /* RDSLBMSB & RDSLBLSB */115u16 rdspb; /* RDSPBMSB & RDSPBLSB */116u16 rdsbc; /* RDSBBC & RDSGBC */117u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */118u16 rdsbbl; /* PAUSEDET & RDSBBL */119u16 manid; /* MANID1 & MANID2 */120u16 chipid; /* CHIPID1 & CHIPID2 */121} __attribute__ ((packed));122123struct tea5764_write_regs {124u8 intreg; /* INTMSK */125u16 frqset; /* FRQSETMSB & FRQSETLSB */126u16 tnctrl; /* TNCTRL1 & TNCTRL2 */127u16 testreg; /* TESTBITS & TESTMODE */128u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */129u16 rdsbbl; /* PAUSEDET & RDSBBL */130} __attribute__ ((packed));131132#ifndef RADIO_TEA5764_XTAL133#define RADIO_TEA5764_XTAL 1134#endif135136static int radio_nr = -1;137static int use_xtal = RADIO_TEA5764_XTAL;138139struct tea5764_device {140struct i2c_client *i2c_client;141struct video_device *videodev;142struct tea5764_regs regs;143struct mutex mutex;144};145146/* I2C code related */147int tea5764_i2c_read(struct tea5764_device *radio)148{149int i;150u16 *p = (u16 *) &radio->regs;151152struct i2c_msg msgs[1] = {153{ radio->i2c_client->addr, I2C_M_RD, sizeof(radio->regs),154(void *)&radio->regs },155};156if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1)157return -EIO;158for (i = 0; i < sizeof(struct tea5764_regs) / sizeof(u16); i++)159p[i] = __be16_to_cpu(p[i]);160161return 0;162}163164int tea5764_i2c_write(struct tea5764_device *radio)165{166struct tea5764_write_regs wr;167struct tea5764_regs *r = &radio->regs;168struct i2c_msg msgs[1] = {169{ radio->i2c_client->addr, 0, sizeof(wr), (void *) &wr },170};171wr.intreg = r->intreg & 0xff;172wr.frqset = __cpu_to_be16(r->frqset);173wr.tnctrl = __cpu_to_be16(r->tnctrl);174wr.testreg = __cpu_to_be16(r->testreg);175wr.rdsctrl = __cpu_to_be16(r->rdsctrl);176wr.rdsbbl = __cpu_to_be16(r->rdsbbl);177if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1)178return -EIO;179return 0;180}181182/* V4L2 code related */183static struct v4l2_queryctrl radio_qctrl[] = {184{185.id = V4L2_CID_AUDIO_MUTE,186.name = "Mute",187.minimum = 0,188.maximum = 1,189.default_value = 1,190.type = V4L2_CTRL_TYPE_BOOLEAN,191}192};193194static void tea5764_power_up(struct tea5764_device *radio)195{196struct tea5764_regs *r = &radio->regs;197198if (!(r->tnctrl & TEA5764_TNCTRL_PUPD0)) {199r->tnctrl &= ~(TEA5764_TNCTRL_AFM | TEA5764_TNCTRL_MU |200TEA5764_TNCTRL_HLSI);201if (!use_xtal)202r->testreg |= TEA5764_TESTREG_TRIGFR;203else204r->testreg &= ~TEA5764_TESTREG_TRIGFR;205206r->tnctrl |= TEA5764_TNCTRL_PUPD0;207tea5764_i2c_write(radio);208}209}210211static void tea5764_power_down(struct tea5764_device *radio)212{213struct tea5764_regs *r = &radio->regs;214215if (r->tnctrl & TEA5764_TNCTRL_PUPD0) {216r->tnctrl &= ~TEA5764_TNCTRL_PUPD0;217tea5764_i2c_write(radio);218}219}220221static void tea5764_set_freq(struct tea5764_device *radio, int freq)222{223struct tea5764_regs *r = &radio->regs;224225/* formula: (freq [+ or -] 225000) / 8192 */226if (r->tnctrl & TEA5764_TNCTRL_HLSI)227r->frqset = (freq + 225000) / 8192;228else229r->frqset = (freq - 225000) / 8192;230}231232static int tea5764_get_freq(struct tea5764_device *radio)233{234struct tea5764_regs *r = &radio->regs;235236if (r->tnctrl & TEA5764_TNCTRL_HLSI)237return (r->frqchk * 8192) - 225000;238else239return (r->frqchk * 8192) + 225000;240}241242/* tune an frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */243static void tea5764_tune(struct tea5764_device *radio, int freq)244{245tea5764_set_freq(radio, freq);246if (tea5764_i2c_write(radio))247PWARN("Could not set frequency!");248}249250static void tea5764_set_audout_mode(struct tea5764_device *radio, int audmode)251{252struct tea5764_regs *r = &radio->regs;253int tnctrl = r->tnctrl;254255if (audmode == V4L2_TUNER_MODE_MONO)256r->tnctrl |= TEA5764_TNCTRL_MST;257else258r->tnctrl &= ~TEA5764_TNCTRL_MST;259if (tnctrl != r->tnctrl)260tea5764_i2c_write(radio);261}262263static int tea5764_get_audout_mode(struct tea5764_device *radio)264{265struct tea5764_regs *r = &radio->regs;266267if (r->tnctrl & TEA5764_TNCTRL_MST)268return V4L2_TUNER_MODE_MONO;269else270return V4L2_TUNER_MODE_STEREO;271}272273static void tea5764_mute(struct tea5764_device *radio, int on)274{275struct tea5764_regs *r = &radio->regs;276int tnctrl = r->tnctrl;277278if (on)279r->tnctrl |= TEA5764_TNCTRL_MU;280else281r->tnctrl &= ~TEA5764_TNCTRL_MU;282if (tnctrl != r->tnctrl)283tea5764_i2c_write(radio);284}285286static int tea5764_is_muted(struct tea5764_device *radio)287{288return radio->regs.tnctrl & TEA5764_TNCTRL_MU;289}290291/* V4L2 vidioc */292static int vidioc_querycap(struct file *file, void *priv,293struct v4l2_capability *v)294{295struct tea5764_device *radio = video_drvdata(file);296struct video_device *dev = radio->videodev;297298strlcpy(v->driver, dev->dev.driver->name, sizeof(v->driver));299strlcpy(v->card, dev->name, sizeof(v->card));300snprintf(v->bus_info, sizeof(v->bus_info),301"I2C:%s", dev_name(&dev->dev));302v->version = RADIO_VERSION;303v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;304return 0;305}306307static int vidioc_g_tuner(struct file *file, void *priv,308struct v4l2_tuner *v)309{310struct tea5764_device *radio = video_drvdata(file);311struct tea5764_regs *r = &radio->regs;312313if (v->index > 0)314return -EINVAL;315316memset(v, 0, sizeof(*v));317strcpy(v->name, "FM");318v->type = V4L2_TUNER_RADIO;319tea5764_i2c_read(radio);320v->rangelow = FREQ_MIN * FREQ_MUL;321v->rangehigh = FREQ_MAX * FREQ_MUL;322v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;323if (r->tunchk & TEA5764_TUNCHK_STEREO)324v->rxsubchans = V4L2_TUNER_SUB_STEREO;325else326v->rxsubchans = V4L2_TUNER_SUB_MONO;327v->audmode = tea5764_get_audout_mode(radio);328v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf;329v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk);330331return 0;332}333334static int vidioc_s_tuner(struct file *file, void *priv,335struct v4l2_tuner *v)336{337struct tea5764_device *radio = video_drvdata(file);338339if (v->index > 0)340return -EINVAL;341342tea5764_set_audout_mode(radio, v->audmode);343return 0;344}345346static int vidioc_s_frequency(struct file *file, void *priv,347struct v4l2_frequency *f)348{349struct tea5764_device *radio = video_drvdata(file);350351if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)352return -EINVAL;353if (f->frequency == 0) {354/* We special case this as a power down control. */355tea5764_power_down(radio);356}357if (f->frequency < (FREQ_MIN * FREQ_MUL))358return -EINVAL;359if (f->frequency > (FREQ_MAX * FREQ_MUL))360return -EINVAL;361tea5764_power_up(radio);362tea5764_tune(radio, (f->frequency * 125) / 2);363return 0;364}365366static int vidioc_g_frequency(struct file *file, void *priv,367struct v4l2_frequency *f)368{369struct tea5764_device *radio = video_drvdata(file);370struct tea5764_regs *r = &radio->regs;371372if (f->tuner != 0)373return -EINVAL;374tea5764_i2c_read(radio);375memset(f, 0, sizeof(*f));376f->type = V4L2_TUNER_RADIO;377if (r->tnctrl & TEA5764_TNCTRL_PUPD0)378f->frequency = (tea5764_get_freq(radio) * 2) / 125;379else380f->frequency = 0;381382return 0;383}384385static int vidioc_queryctrl(struct file *file, void *priv,386struct v4l2_queryctrl *qc)387{388int i;389390for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {391if (qc->id && qc->id == radio_qctrl[i].id) {392memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));393return 0;394}395}396return -EINVAL;397}398399static int vidioc_g_ctrl(struct file *file, void *priv,400struct v4l2_control *ctrl)401{402struct tea5764_device *radio = video_drvdata(file);403404switch (ctrl->id) {405case V4L2_CID_AUDIO_MUTE:406tea5764_i2c_read(radio);407ctrl->value = tea5764_is_muted(radio) ? 1 : 0;408return 0;409}410return -EINVAL;411}412413static int vidioc_s_ctrl(struct file *file, void *priv,414struct v4l2_control *ctrl)415{416struct tea5764_device *radio = video_drvdata(file);417418switch (ctrl->id) {419case V4L2_CID_AUDIO_MUTE:420tea5764_mute(radio, ctrl->value);421return 0;422}423return -EINVAL;424}425426static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)427{428*i = 0;429return 0;430}431432static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)433{434if (i != 0)435return -EINVAL;436return 0;437}438439static int vidioc_g_audio(struct file *file, void *priv,440struct v4l2_audio *a)441{442if (a->index > 1)443return -EINVAL;444445strcpy(a->name, "Radio");446a->capability = V4L2_AUDCAP_STEREO;447return 0;448}449450static int vidioc_s_audio(struct file *file, void *priv,451struct v4l2_audio *a)452{453if (a->index != 0)454return -EINVAL;455456return 0;457}458459/* File system interface */460static const struct v4l2_file_operations tea5764_fops = {461.owner = THIS_MODULE,462.unlocked_ioctl = video_ioctl2,463};464465static const struct v4l2_ioctl_ops tea5764_ioctl_ops = {466.vidioc_querycap = vidioc_querycap,467.vidioc_g_tuner = vidioc_g_tuner,468.vidioc_s_tuner = vidioc_s_tuner,469.vidioc_g_audio = vidioc_g_audio,470.vidioc_s_audio = vidioc_s_audio,471.vidioc_g_input = vidioc_g_input,472.vidioc_s_input = vidioc_s_input,473.vidioc_g_frequency = vidioc_g_frequency,474.vidioc_s_frequency = vidioc_s_frequency,475.vidioc_queryctrl = vidioc_queryctrl,476.vidioc_g_ctrl = vidioc_g_ctrl,477.vidioc_s_ctrl = vidioc_s_ctrl,478};479480/* V4L2 interface */481static struct video_device tea5764_radio_template = {482.name = "TEA5764 FM-Radio",483.fops = &tea5764_fops,484.ioctl_ops = &tea5764_ioctl_ops,485.release = video_device_release,486};487488/* I2C probe: check if the device exists and register with v4l if it is */489static int __devinit tea5764_i2c_probe(struct i2c_client *client,490const struct i2c_device_id *id)491{492struct tea5764_device *radio;493struct tea5764_regs *r;494int ret;495496PDEBUG("probe");497radio = kzalloc(sizeof(struct tea5764_device), GFP_KERNEL);498if (!radio)499return -ENOMEM;500501mutex_init(&radio->mutex);502radio->i2c_client = client;503ret = tea5764_i2c_read(radio);504if (ret)505goto errfr;506r = &radio->regs;507PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid);508if (r->chipid != TEA5764_CHIPID ||509(r->manid & 0x0fff) != TEA5764_MANID) {510PWARN("This chip is not a TEA5764!");511ret = -EINVAL;512goto errfr;513}514515radio->videodev = video_device_alloc();516if (!(radio->videodev)) {517ret = -ENOMEM;518goto errfr;519}520memcpy(radio->videodev, &tea5764_radio_template,521sizeof(tea5764_radio_template));522523i2c_set_clientdata(client, radio);524video_set_drvdata(radio->videodev, radio);525radio->videodev->lock = &radio->mutex;526527/* initialize and power off the chip */528tea5764_i2c_read(radio);529tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO);530tea5764_mute(radio, 1);531tea5764_power_down(radio);532533ret = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);534if (ret < 0) {535PWARN("Could not register video device!");536goto errrel;537}538539PINFO("registered.");540return 0;541errrel:542video_device_release(radio->videodev);543errfr:544kfree(radio);545return ret;546}547548static int __devexit tea5764_i2c_remove(struct i2c_client *client)549{550struct tea5764_device *radio = i2c_get_clientdata(client);551552PDEBUG("remove");553if (radio) {554tea5764_power_down(radio);555video_unregister_device(radio->videodev);556kfree(radio);557}558return 0;559}560561/* I2C subsystem interface */562static const struct i2c_device_id tea5764_id[] = {563{ "radio-tea5764", 0 },564{ } /* Terminating entry */565};566MODULE_DEVICE_TABLE(i2c, tea5764_id);567568static struct i2c_driver tea5764_i2c_driver = {569.driver = {570.name = "radio-tea5764",571.owner = THIS_MODULE,572},573.probe = tea5764_i2c_probe,574.remove = __devexit_p(tea5764_i2c_remove),575.id_table = tea5764_id,576};577578/* init the driver */579static int __init tea5764_init(void)580{581int ret = i2c_add_driver(&tea5764_i2c_driver);582583printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ": "584DRIVER_DESC "\n");585return ret;586}587588/* cleanup the driver */589static void __exit tea5764_exit(void)590{591i2c_del_driver(&tea5764_i2c_driver);592}593594MODULE_AUTHOR(DRIVER_AUTHOR);595MODULE_DESCRIPTION(DRIVER_DESC);596MODULE_LICENSE("GPL");597598module_param(use_xtal, int, 1);599MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board");600module_param(radio_nr, int, 0);601MODULE_PARM_DESC(radio_nr, "video4linux device number to use");602603module_init(tea5764_init);604module_exit(tea5764_exit);605606607