radio-maxiradio.c 8.02 KB
Newer Older
1 2
/*
 * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux
Linus Torvalds's avatar
Linus Torvalds committed
3 4 5 6 7 8
 * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net>
 *
 * Based in the radio Maestro PCI driver. Actually it uses the same chip
 * for radio but different pci controller.
 *
 * I didn't have any specs I reversed engineered the protocol from
9
 * the windows driver (radio.dll).
Linus Torvalds's avatar
Linus Torvalds committed
10 11
 *
 * The card uses the TEA5757 chip that includes a search function but it
12
 * is useless as I haven't found any way to read back the frequency. If
Linus Torvalds's avatar
Linus Torvalds committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26
 * anybody does please mail me.
 *
 * For the pdf file see:
 * http://www.semiconductors.philips.com/pip/TEA5757H/V1
 *
 *
 * CHANGES:
 *   0.75b
 *     - better pci interface thanks to Francois Romieu <romieu@cogenit.fr>
 *
 *   0.75
 *     - tiding up
 *     - removed support for multiple devices as it didn't work anyway
 *
27
 * BUGS:
Linus Torvalds's avatar
Linus Torvalds committed
28 29 30 31 32 33 34 35 36 37 38 39
 *   - card unmutes if you change frequency
 *
 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/uaccess.h>
40 41
#include <linux/mutex.h>

Linus Torvalds's avatar
Linus Torvalds committed
42 43
#include <linux/pci.h>
#include <linux/videodev.h>
44
#include <media/v4l2-common.h>
Linus Torvalds's avatar
Linus Torvalds committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

/* version 0.75      Sun Feb  4 22:51:27 EET 2001 */
#define DRIVER_VERSION	"0.75"

#ifndef PCI_VENDOR_ID_GUILLEMOT
#define PCI_VENDOR_ID_GUILLEMOT 0x5046
#endif

#ifndef PCI_DEVICE_ID_GUILLEMOT
#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001
#endif


/* TEA5757 pin mappings */
static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16 ;

static int radio_nr = -1;
module_param(radio_nr, int, 0);


#define FREQ_LO		 50*16000
#define FREQ_HI		150*16000

#define FREQ_IF         171200 /* 10.7*16000   */
#define FREQ_STEP       200    /* 12.5*16      */

#define FREQ2BITS(x)	((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\
			/(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */

#define BITS2FREQ(x)	((x) * FREQ_STEP - FREQ_IF)


static int radio_ioctl(struct inode *inode, struct file *file,
		       unsigned int cmd, unsigned long arg);

static struct file_operations maxiradio_fops = {
	.owner		= THIS_MODULE,
	.open           = video_exclusive_open,
	.release        = video_exclusive_release,
84
	.ioctl		= radio_ioctl,
85
	.compat_ioctl	= v4l_compat_ioctl32,
Linus Torvalds's avatar
Linus Torvalds committed
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
	.llseek         = no_llseek,
};
static struct video_device maxiradio_radio =
{
	.owner		= THIS_MODULE,
	.name		= "Maxi Radio FM2000 radio",
	.type		= VID_TYPE_TUNER,
	.hardware	= VID_HARDWARE_SF16MI,
	.fops           = &maxiradio_fops,
};

static struct radio_device
{
	__u16	io,	/* base of radio io */
		muted,	/* VIDEO_AUDIO_MUTE */
101
		stereo,	/* VIDEO_TUNER_STEREO_ON */
Linus Torvalds's avatar
Linus Torvalds committed
102
		tuned;	/* signal strength (0 or 0xffff) */
103

Linus Torvalds's avatar
Linus Torvalds committed
104
	unsigned long freq;
105

106
	struct mutex lock;
Linus Torvalds's avatar
Linus Torvalds committed
107 108 109 110 111 112 113 114 115 116 117
} radio_unit = {0, 0, 0, 0, };


static void outbit(unsigned long bit, __u16 io)
{
	if(bit != 0)
		{
			outb(  power|wren|data     ,io); udelay(4);
			outb(  power|wren|data|clk ,io); udelay(4);
			outb(  power|wren|data     ,io); udelay(4);
		}
118
	else
Linus Torvalds's avatar
Linus Torvalds committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
		{
			outb(  power|wren          ,io); udelay(4);
			outb(  power|wren|clk      ,io); udelay(4);
			outb(  power|wren          ,io); udelay(4);
		}
}

static void turn_power(__u16 io, int p)
{
	if(p != 0) outb(power, io); else outb(0,io);
}


static void set_freq(__u16 io, __u32 data)
{
	unsigned long int si;
	int bl;
136

Linus Torvalds's avatar
Linus Torvalds committed
137 138
	/* TEA5757 shift register bits (see pdf) */

139
	outbit(0,io); // 24  search
Linus Torvalds's avatar
Linus Torvalds committed
140
	outbit(1,io); // 23  search up/down
141

Linus Torvalds's avatar
Linus Torvalds committed
142 143 144 145 146 147 148
	outbit(0,io); // 22  stereo/mono

	outbit(0,io); // 21  band
	outbit(0,io); // 20  band (only 00=FM works I think)

	outbit(0,io); // 19  port ?
	outbit(0,io); // 18  port ?
149

Linus Torvalds's avatar
Linus Torvalds committed
150 151
	outbit(0,io); // 17  search level
	outbit(0,io); // 16  search level
152

Linus Torvalds's avatar
Linus Torvalds committed
153 154
	si = 0x8000;
	for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; }
155

Linus Torvalds's avatar
Linus Torvalds committed
156 157 158 159
	outb(power,io);
}

static int get_stereo(__u16 io)
160
{
Linus Torvalds's avatar
Linus Torvalds committed
161 162 163 164 165
	outb(power,io); udelay(4);
	return !(inb(io) & mo_st);
}

static int get_tune(__u16 io)
166
{
Linus Torvalds's avatar
Linus Torvalds committed
167 168 169 170 171
	outb(power+clk,io); udelay(4);
	return !(inb(io) & mo_st);
}


172
static inline int radio_function(struct inode *inode, struct file *file,
Linus Torvalds's avatar
Linus Torvalds committed
173 174 175 176 177 178 179 180
				 unsigned int cmd, void *arg)
{
	struct video_device *dev = video_devdata(file);
	struct radio_device *card=dev->priv;

	switch(cmd) {
		case VIDIOCGCAP: {
			struct video_capability *v = arg;
181

Linus Torvalds's avatar
Linus Torvalds committed
182 183 184 185 186 187 188 189
			memset(v,0,sizeof(*v));
			strcpy(v->name, "Maxi Radio FM2000 radio");
			v->type=VID_TYPE_TUNER;
			v->channels=v->audios=1;
			return 0;
		}
		case VIDIOCGTUNER: {
			struct video_tuner *v = arg;
190

Linus Torvalds's avatar
Linus Torvalds committed
191 192
			if(v->tuner)
				return -EINVAL;
193

Linus Torvalds's avatar
Linus Torvalds committed
194 195
			card->stereo = 0xffff * get_stereo(card->io);
			card->tuned = 0xffff * get_tune(card->io);
196

Linus Torvalds's avatar
Linus Torvalds committed
197 198
			v->flags = VIDEO_TUNER_LOW | card->stereo;
			v->signal = card->tuned;
199

Linus Torvalds's avatar
Linus Torvalds committed
200
			strcpy(v->name, "FM");
201

Linus Torvalds's avatar
Linus Torvalds committed
202 203 204
			v->rangelow = FREQ_LO;
			v->rangehigh = FREQ_HI;
			v->mode = VIDEO_MODE_AUTO;
205

Linus Torvalds's avatar
Linus Torvalds committed
206 207 208 209 210 211 212 213 214 215
			return 0;
		}
		case VIDIOCSTUNER: {
			struct video_tuner *v = arg;
			if(v->tuner!=0)
				return -EINVAL;
			return 0;
		}
		case VIDIOCGFREQ: {
			unsigned long *freq = arg;
216

Linus Torvalds's avatar
Linus Torvalds committed
217 218 219 220 221
			*freq = card->freq;
			return 0;
		}
		case VIDIOCSFREQ: {
			unsigned long *freq = arg;
222

Linus Torvalds's avatar
Linus Torvalds committed
223 224 225 226 227 228 229
			if (*freq < FREQ_LO || *freq > FREQ_HI)
				return -EINVAL;
			card->freq = *freq;
			set_freq(card->io, FREQ2BITS(card->freq));
			msleep(125);
			return 0;
		}
230
		case VIDIOCGAUDIO: {
Linus Torvalds's avatar
Linus Torvalds committed
231 232 233 234 235
			struct video_audio *v = arg;
			memset(v,0,sizeof(*v));
			strcpy(v->name, "Radio");
			v->flags=VIDEO_AUDIO_MUTABLE | card->muted;
			v->mode=VIDEO_SOUND_STEREO;
236
			return 0;
Linus Torvalds's avatar
Linus Torvalds committed
237
		}
238

Linus Torvalds's avatar
Linus Torvalds committed
239 240
		case VIDIOCSAUDIO: {
			struct video_audio *v = arg;
241

Linus Torvalds's avatar
Linus Torvalds committed
242 243 244 245 246 247 248 249 250 251 252
			if(v->audio)
				return -EINVAL;
			card->muted = v->flags & VIDEO_AUDIO_MUTE;
			if(card->muted)
				turn_power(card->io, 0);
			else
				set_freq(card->io, FREQ2BITS(card->freq));
			return 0;
		}
		case VIDIOCGUNIT: {
			struct video_unit *v = arg;
253

Linus Torvalds's avatar
Linus Torvalds committed
254 255 256 257 258
			v->video=VIDEO_NO_UNIT;
			v->vbi=VIDEO_NO_UNIT;
			v->radio=dev->minor;
			v->audio=0;
			v->teletext=VIDEO_NO_UNIT;
259
			return 0;
Linus Torvalds's avatar
Linus Torvalds committed
260 261 262 263 264 265 266 267 268 269 270
		}
		default: return -ENOIOCTLCMD;
	}
}

static int radio_ioctl(struct inode *inode, struct file *file,
		       unsigned int cmd, unsigned long arg)
{
	struct video_device *dev = video_devdata(file);
	struct radio_device *card=dev->priv;
	int ret;
271

272
	mutex_lock(&card->lock);
Linus Torvalds's avatar
Linus Torvalds committed
273
	ret = video_usercopy(inode, file, cmd, arg, radio_function);
274
	mutex_unlock(&card->lock);
Linus Torvalds's avatar
Linus Torvalds committed
275 276 277 278 279 280 281 282 283 284 285
	return ret;
}

MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net");
MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio.");
MODULE_LICENSE("GPL");


static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	if(!request_region(pci_resource_start(pdev, 0),
286 287 288
			   pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) {
		printk(KERN_ERR "radio-maxiradio: can't reserve I/O ports\n");
		goto err_out;
Linus Torvalds's avatar
Linus Torvalds committed
289 290 291
	}

	if (pci_enable_device(pdev))
292
		goto err_out_free_region;
Linus Torvalds's avatar
Linus Torvalds committed
293 294

	radio_unit.io = pci_resource_start(pdev, 0);
295
	mutex_init(&radio_unit.lock);
Linus Torvalds's avatar
Linus Torvalds committed
296 297 298
	maxiradio_radio.priv = &radio_unit;

	if(video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr)==-1) {
299 300
		printk("radio-maxiradio: can't register device!");
		goto err_out_free_region;
Linus Torvalds's avatar
Linus Torvalds committed
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
	}

	printk(KERN_INFO "radio-maxiradio: version "
	       DRIVER_VERSION
	       " time "
	       __TIME__ "  "
	       __DATE__
	       "\n");

	printk(KERN_INFO "radio-maxiradio: found Guillemot MAXI Radio device (io = 0x%x)\n",
	       radio_unit.io);
	return 0;

err_out_free_region:
	release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
err_out:
	return -ENODEV;
}

static void __devexit maxiradio_remove_one(struct pci_dev *pdev)
{
	video_unregister_device(&maxiradio_radio);
	release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
}

static struct pci_device_id maxiradio_pci_tbl[] = {
	{ PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO,
		PCI_ANY_ID, PCI_ANY_ID, },
	{ 0,}
};

MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl);

static struct pci_driver maxiradio_driver = {
	.name		= "radio-maxiradio",
	.id_table	= maxiradio_pci_tbl,
	.probe		= maxiradio_init_one,
	.remove		= __devexit_p(maxiradio_remove_one),
};

static int __init maxiradio_radio_init(void)
{
343
	return pci_register_driver(&maxiradio_driver);
Linus Torvalds's avatar
Linus Torvalds committed
344 345 346 347 348 349 350 351 352
}

static void __exit maxiradio_radio_exit(void)
{
	pci_unregister_driver(&maxiradio_driver);
}

module_init(maxiradio_radio_init);
module_exit(maxiradio_radio_exit);