radio-maxiradio.c 8.0 KB
Newer Older
1 2
/*
 * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux
L
Linus Torvalds 已提交
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).
L
Linus Torvalds 已提交
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
L
Linus Torvalds 已提交
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:
L
Linus Torvalds 已提交
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>

L
Linus Torvalds 已提交
42 43
#include <linux/pci.h>
#include <linux/videodev.h>
44
#include <media/v4l2-common.h>
L
Linus Torvalds 已提交
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,
L
Linus Torvalds 已提交
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 */
L
Linus Torvalds 已提交
102
		tuned;	/* signal strength (0 or 0xffff) */
103

L
Linus Torvalds 已提交
104
	unsigned long freq;
105

106
	struct mutex lock;
L
Linus Torvalds 已提交
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
L
Linus Torvalds 已提交
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

L
Linus Torvalds 已提交
137 138
	/* TEA5757 shift register bits (see pdf) */

139
	outbit(0,io); // 24  search
L
Linus Torvalds 已提交
140
	outbit(1,io); // 23  search up/down
141

L
Linus Torvalds 已提交
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

L
Linus Torvalds 已提交
150 151
	outbit(0,io); // 17  search level
	outbit(0,io); // 16  search level
152

L
Linus Torvalds 已提交
153 154
	si = 0x8000;
	for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; }
155

L
Linus Torvalds 已提交
156 157 158 159
	outb(power,io);
}

static int get_stereo(__u16 io)
160
{
L
Linus Torvalds 已提交
161 162 163 164 165
	outb(power,io); udelay(4);
	return !(inb(io) & mo_st);
}

static int get_tune(__u16 io)
166
{
L
Linus Torvalds 已提交
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,
L
Linus Torvalds 已提交
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

L
Linus Torvalds 已提交
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

L
Linus Torvalds 已提交
191 192
			if(v->tuner)
				return -EINVAL;
193

L
Linus Torvalds 已提交
194 195
			card->stereo = 0xffff * get_stereo(card->io);
			card->tuned = 0xffff * get_tune(card->io);
196

L
Linus Torvalds 已提交
197 198
			v->flags = VIDEO_TUNER_LOW | card->stereo;
			v->signal = card->tuned;
199

L
Linus Torvalds 已提交
200
			strcpy(v->name, "FM");
201

L
Linus Torvalds 已提交
202 203 204
			v->rangelow = FREQ_LO;
			v->rangehigh = FREQ_HI;
			v->mode = VIDEO_MODE_AUTO;
205

L
Linus Torvalds 已提交
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

L
Linus Torvalds 已提交
217 218 219 220 221
			*freq = card->freq;
			return 0;
		}
		case VIDIOCSFREQ: {
			unsigned long *freq = arg;
222

L
Linus Torvalds 已提交
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: {
L
Linus Torvalds 已提交
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;
L
Linus Torvalds 已提交
237
		}
238

L
Linus Torvalds 已提交
239 240
		case VIDIOCSAUDIO: {
			struct video_audio *v = arg;
241

L
Linus Torvalds 已提交
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

L
Linus Torvalds 已提交
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;
L
Linus Torvalds 已提交
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);
L
Linus Torvalds 已提交
273
	ret = video_usercopy(inode, file, cmd, arg, radio_function);
274
	mutex_unlock(&card->lock);
L
Linus Torvalds 已提交
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;
L
Linus Torvalds 已提交
289 290 291
	}

	if (pci_enable_device(pdev))
292
		goto err_out_free_region;
L
Linus Torvalds 已提交
293 294

	radio_unit.io = pci_resource_start(pdev, 0);
295
	mutex_init(&radio_unit.lock);
L
Linus Torvalds 已提交
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;
L
Linus Torvalds 已提交
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);
L
Linus Torvalds 已提交
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);