Path: blob/master/drivers/media/radio/radio-maxiradio.c
15112 views
/*1* Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux2* (C) 2001 Dimitromanolakis Apostolos <[email protected]>3*4* Based in the radio Maestro PCI driver. Actually it uses the same chip5* for radio but different pci controller.6*7* I didn't have any specs I reversed engineered the protocol from8* the windows driver (radio.dll).9*10* The card uses the TEA5757 chip that includes a search function but it11* is useless as I haven't found any way to read back the frequency. If12* anybody does please mail me.13*14* For the pdf file see:15* http://www.nxp.com/acrobat_download2/expired_datasheets/TEA5757_5759_3.pdf16*17*18* CHANGES:19* 0.75b20* - better pci interface thanks to Francois Romieu <[email protected]>21*22* 0.75 Sun Feb 4 22:51:27 EET 200123* - tiding up24* - removed support for multiple devices as it didn't work anyway25*26* BUGS:27* - card unmutes if you change frequency28*29* (c) 2006, 2007 by Mauro Carvalho Chehab <[email protected]>:30* - Conversion to V4L2 API31* - Uses video_ioctl2 for parsing and to add debug support32*/333435#include <linux/module.h>36#include <linux/init.h>37#include <linux/ioport.h>38#include <linux/delay.h>39#include <linux/mutex.h>40#include <linux/pci.h>41#include <linux/videodev2.h>42#include <linux/version.h> /* for KERNEL_VERSION MACRO */43#include <linux/io.h>44#include <linux/slab.h>45#include <media/v4l2-device.h>46#include <media/v4l2-ioctl.h>4748MODULE_AUTHOR("Dimitromanolakis Apostolos, [email protected]");49MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio.");50MODULE_LICENSE("GPL");5152static int radio_nr = -1;53module_param(radio_nr, int, 0);5455static int debug;5657module_param(debug, int, 0644);58MODULE_PARM_DESC(debug, "activates debug info");5960#define DRIVER_VERSION "0.77"6162#define RADIO_VERSION KERNEL_VERSION(0, 7, 7)6364#define dprintk(dev, num, fmt, arg...) \65v4l2_dbg(num, debug, &dev->v4l2_dev, fmt, ## arg)6667#ifndef PCI_VENDOR_ID_GUILLEMOT68#define PCI_VENDOR_ID_GUILLEMOT 0x504669#endif7071#ifndef PCI_DEVICE_ID_GUILLEMOT72#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x100173#endif747576/* TEA5757 pin mappings */77static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16;7879#define FREQ_LO (87 * 16000)80#define FREQ_HI (108 * 16000)8182#define FREQ_IF 171200 /* 10.7*16000 */83#define FREQ_STEP 200 /* 12.5*16 */8485/* (x==fmhz*16*1000) -> bits */86#define FREQ2BITS(x) \87((((unsigned int)(x) + FREQ_IF + (FREQ_STEP << 1)) / (FREQ_STEP << 2)) << 2)8889#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF)909192struct maxiradio93{94struct v4l2_device v4l2_dev;95struct video_device vdev;96struct pci_dev *pdev;9798u16 io; /* base of radio io */99u16 muted; /* VIDEO_AUDIO_MUTE */100u16 stereo; /* VIDEO_TUNER_STEREO_ON */101u16 tuned; /* signal strength (0 or 0xffff) */102103unsigned long freq;104105struct mutex lock;106};107108static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev)109{110return container_of(v4l2_dev, struct maxiradio, v4l2_dev);111}112113static void outbit(unsigned long bit, u16 io)114{115int val = power | wren | (bit ? data : 0);116117outb(val, io);118udelay(4);119outb(val | clk, io);120udelay(4);121outb(val, io);122udelay(4);123}124125static void turn_power(struct maxiradio *dev, int p)126{127if (p != 0) {128dprintk(dev, 1, "Radio powered on\n");129outb(power, dev->io);130} else {131dprintk(dev, 1, "Radio powered off\n");132outb(0, dev->io);133}134}135136static void set_freq(struct maxiradio *dev, u32 freq)137{138unsigned long int si;139int bl;140int io = dev->io;141int val = FREQ2BITS(freq);142143/* TEA5757 shift register bits (see pdf) */144145outbit(0, io); /* 24 search */146outbit(1, io); /* 23 search up/down */147148outbit(0, io); /* 22 stereo/mono */149150outbit(0, io); /* 21 band */151outbit(0, io); /* 20 band (only 00=FM works I think) */152153outbit(0, io); /* 19 port ? */154outbit(0, io); /* 18 port ? */155156outbit(0, io); /* 17 search level */157outbit(0, io); /* 16 search level */158159si = 0x8000;160for (bl = 1; bl <= 16; bl++) {161outbit(val & si, io);162si >>= 1;163}164165dprintk(dev, 1, "Radio freq set to %d.%02d MHz\n",166freq / 16000,167freq % 16000 * 100 / 16000);168169turn_power(dev, 1);170}171172static int get_stereo(u16 io)173{174outb(power,io);175udelay(4);176177return !(inb(io) & mo_st);178}179180static int get_tune(u16 io)181{182outb(power+clk,io);183udelay(4);184185return !(inb(io) & mo_st);186}187188189static int vidioc_querycap(struct file *file, void *priv,190struct v4l2_capability *v)191{192struct maxiradio *dev = video_drvdata(file);193194strlcpy(v->driver, "radio-maxiradio", sizeof(v->driver));195strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof(v->card));196snprintf(v->bus_info, sizeof(v->bus_info), "PCI:%s", pci_name(dev->pdev));197v->version = RADIO_VERSION;198v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;199return 0;200}201202static int vidioc_g_tuner(struct file *file, void *priv,203struct v4l2_tuner *v)204{205struct maxiradio *dev = video_drvdata(file);206207if (v->index > 0)208return -EINVAL;209210mutex_lock(&dev->lock);211strlcpy(v->name, "FM", sizeof(v->name));212v->type = V4L2_TUNER_RADIO;213v->rangelow = FREQ_LO;214v->rangehigh = FREQ_HI;215v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;216v->capability = V4L2_TUNER_CAP_LOW;217if (get_stereo(dev->io))218v->audmode = V4L2_TUNER_MODE_STEREO;219else220v->audmode = V4L2_TUNER_MODE_MONO;221v->signal = 0xffff * get_tune(dev->io);222mutex_unlock(&dev->lock);223224return 0;225}226227static int vidioc_s_tuner(struct file *file, void *priv,228struct v4l2_tuner *v)229{230return v->index ? -EINVAL : 0;231}232233static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)234{235*i = 0;236return 0;237}238239static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)240{241return i ? -EINVAL : 0;242}243244static int vidioc_g_audio(struct file *file, void *priv,245struct v4l2_audio *a)246{247a->index = 0;248strlcpy(a->name, "Radio", sizeof(a->name));249a->capability = V4L2_AUDCAP_STEREO;250return 0;251}252253254static int vidioc_s_audio(struct file *file, void *priv,255struct v4l2_audio *a)256{257return a->index ? -EINVAL : 0;258}259260static int vidioc_s_frequency(struct file *file, void *priv,261struct v4l2_frequency *f)262{263struct maxiradio *dev = video_drvdata(file);264265if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)266return -EINVAL;267if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) {268dprintk(dev, 1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n",269f->frequency / 16000,270f->frequency % 16000 * 100 / 16000,271FREQ_LO / 16000, FREQ_HI / 16000);272273return -EINVAL;274}275276mutex_lock(&dev->lock);277dev->freq = f->frequency;278set_freq(dev, dev->freq);279msleep(125);280mutex_unlock(&dev->lock);281282return 0;283}284285static int vidioc_g_frequency(struct file *file, void *priv,286struct v4l2_frequency *f)287{288struct maxiradio *dev = video_drvdata(file);289290if (f->tuner != 0)291return -EINVAL;292f->type = V4L2_TUNER_RADIO;293f->frequency = dev->freq;294295dprintk(dev, 4, "radio freq is %d.%02d MHz",296f->frequency / 16000,297f->frequency % 16000 * 100 / 16000);298299return 0;300}301302static int vidioc_queryctrl(struct file *file, void *priv,303struct v4l2_queryctrl *qc)304{305switch (qc->id) {306case V4L2_CID_AUDIO_MUTE:307return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);308}309return -EINVAL;310}311312static int vidioc_g_ctrl(struct file *file, void *priv,313struct v4l2_control *ctrl)314{315struct maxiradio *dev = video_drvdata(file);316317switch (ctrl->id) {318case V4L2_CID_AUDIO_MUTE:319ctrl->value = dev->muted;320return 0;321}322323return -EINVAL;324}325326static int vidioc_s_ctrl(struct file *file, void *priv,327struct v4l2_control *ctrl)328{329struct maxiradio *dev = video_drvdata(file);330331switch (ctrl->id) {332case V4L2_CID_AUDIO_MUTE:333mutex_lock(&dev->lock);334dev->muted = ctrl->value;335if (dev->muted)336turn_power(dev, 0);337else338set_freq(dev, dev->freq);339mutex_unlock(&dev->lock);340return 0;341}342343return -EINVAL;344}345346static const struct v4l2_file_operations maxiradio_fops = {347.owner = THIS_MODULE,348.unlocked_ioctl = video_ioctl2,349};350351static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = {352.vidioc_querycap = vidioc_querycap,353.vidioc_g_tuner = vidioc_g_tuner,354.vidioc_s_tuner = vidioc_s_tuner,355.vidioc_g_audio = vidioc_g_audio,356.vidioc_s_audio = vidioc_s_audio,357.vidioc_g_input = vidioc_g_input,358.vidioc_s_input = vidioc_s_input,359.vidioc_g_frequency = vidioc_g_frequency,360.vidioc_s_frequency = vidioc_s_frequency,361.vidioc_queryctrl = vidioc_queryctrl,362.vidioc_g_ctrl = vidioc_g_ctrl,363.vidioc_s_ctrl = vidioc_s_ctrl,364};365366static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)367{368struct maxiradio *dev;369struct v4l2_device *v4l2_dev;370int retval = -ENOMEM;371372dev = kzalloc(sizeof(*dev), GFP_KERNEL);373if (dev == NULL) {374dev_err(&pdev->dev, "not enough memory\n");375return -ENOMEM;376}377378v4l2_dev = &dev->v4l2_dev;379mutex_init(&dev->lock);380dev->pdev = pdev;381dev->muted = 1;382dev->freq = FREQ_LO;383384strlcpy(v4l2_dev->name, "maxiradio", sizeof(v4l2_dev->name));385386retval = v4l2_device_register(&pdev->dev, v4l2_dev);387if (retval < 0) {388v4l2_err(v4l2_dev, "Could not register v4l2_device\n");389goto errfr;390}391392if (!request_region(pci_resource_start(pdev, 0),393pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) {394v4l2_err(v4l2_dev, "can't reserve I/O ports\n");395goto err_out;396}397398if (pci_enable_device(pdev))399goto err_out_free_region;400401dev->io = pci_resource_start(pdev, 0);402strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));403dev->vdev.v4l2_dev = v4l2_dev;404dev->vdev.fops = &maxiradio_fops;405dev->vdev.ioctl_ops = &maxiradio_ioctl_ops;406dev->vdev.release = video_device_release_empty;407video_set_drvdata(&dev->vdev, dev);408409if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {410v4l2_err(v4l2_dev, "can't register device!");411goto err_out_free_region;412}413414v4l2_info(v4l2_dev, "version " DRIVER_VERSION "\n");415416v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n",417dev->io);418return 0;419420err_out_free_region:421release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));422err_out:423v4l2_device_unregister(v4l2_dev);424errfr:425kfree(dev);426return -ENODEV;427}428429static void __devexit maxiradio_remove_one(struct pci_dev *pdev)430{431struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev);432struct maxiradio *dev = to_maxiradio(v4l2_dev);433434video_unregister_device(&dev->vdev);435v4l2_device_unregister(&dev->v4l2_dev);436release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));437}438439static struct pci_device_id maxiradio_pci_tbl[] = {440{ PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO,441PCI_ANY_ID, PCI_ANY_ID, },442{ 0 }443};444445MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl);446447static struct pci_driver maxiradio_driver = {448.name = "radio-maxiradio",449.id_table = maxiradio_pci_tbl,450.probe = maxiradio_init_one,451.remove = __devexit_p(maxiradio_remove_one),452};453454static int __init maxiradio_radio_init(void)455{456return pci_register_driver(&maxiradio_driver);457}458459static void __exit maxiradio_radio_exit(void)460{461pci_unregister_driver(&maxiradio_driver);462}463464module_init(maxiradio_radio_init);465module_exit(maxiradio_radio_exit);466467468