1/* radio-aztech.c - Aztech radio card driver for Linux 2.2 2 * 3 * Adapted to support the Video for Linux API by 4 * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: 5 * 6 * Quay Ly 7 * Donald Song 8 * Jason Lewis (jlewis@twilight.vtc.vsc.edu) 9 * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) 10 * William McGrath (wmcgrath@twilight.vtc.vsc.edu) 11 * 12 * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ 13 * along with more information on the card itself. 14 * 15 * History: 16 * 1999-02-24 Russell Kroll <rkroll@exploits.org> 17 * Fine tuning/VIDEO_TUNER_LOW 18 * Range expanded to 87-108 MHz (from 87.9-107.8) 19 * 20 * Notable changes from the original source: 21 * - includes stripped down to the essentials 22 * - for loops used as delays replaced with udelay() 23 * - #defines removed, changed to static values 24 * - tuning structure changed - no more character arrays, other changes 25*/ 26 27#include <linux/module.h> /* Modules */ 28#include <linux/init.h> /* Initdata */ 29#include <linux/ioport.h> /* check_region, request_region */ 30#include <linux/delay.h> /* udelay */ 31#include <asm/io.h> /* outb, outb_p */ 32#include <asm/uaccess.h> /* copy to/from user */ 33#include <linux/videodev.h> /* kernel radio structs */ 34#include <linux/config.h> /* CONFIG_RADIO_AZTECH_PORT */ 35 36/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ 37 38#ifndef CONFIG_RADIO_AZTECH_PORT 39#define CONFIG_RADIO_AZTECH_PORT -1 40#endif 41 42static int io = CONFIG_RADIO_AZTECH_PORT; 43static int radio_nr = -1; 44static int radio_wait_time = 1000; 45static int users = 0; 46static struct semaphore lock; 47 48struct az_device 49{ 50 int curvol; 51 unsigned long curfreq; 52 int stereo; 53}; 54 55static int volconvert(int level) 56{ 57 level>>=14; /* Map 16bits down to 2 bit */ 58 level&=3; 59 60 /* convert to card-friendly values */ 61 switch (level) 62 { 63 case 0: 64 return 0; 65 case 1: 66 return 1; 67 case 2: 68 return 4; 69 case 3: 70 return 5; 71 } 72 return 0; /* Quieten gcc */ 73} 74 75static void send_0_byte (struct az_device *dev) 76{ 77 udelay(radio_wait_time); 78 outb_p(2+volconvert(dev->curvol), io); 79 outb_p(64+2+volconvert(dev->curvol), io); 80} 81 82static void send_1_byte (struct az_device *dev) 83{ 84 udelay (radio_wait_time); 85 outb_p(128+2+volconvert(dev->curvol), io); 86 outb_p(128+64+2+volconvert(dev->curvol), io); 87} 88 89static int az_setvol(struct az_device *dev, int vol) 90{ 91 down(&lock); 92 outb (volconvert(vol), io); 93 up(&lock); 94 return 0; 95} 96 97/* thanks to Michael Dwyer for giving me a dose of clues in 98 * the signal strength department.. 99 * 100 * This card has a stereo bit - bit 0 set = mono, not set = stereo 101 * It also has a "signal" bit - bit 1 set = bad signal, not set = good 102 * 103 */ 104 105static int az_getsigstr(struct az_device *dev) 106{ 107 if (inb(io) & 2) /* bit set = no signal present */ 108 return 0; 109 return 1; /* signal present */ 110} 111 112static int az_getstereo(struct az_device *dev) 113{ 114 if (inb(io) & 1) /* bit set = mono */ 115 return 0; 116 return 1; /* stereo */ 117} 118 119static int az_setfreq(struct az_device *dev, unsigned long frequency) 120{ 121 int i; 122 123 frequency += 171200; /* Add 10.7 MHz IF */ 124 frequency /= 800; /* Convert to 50 kHz units */ 125 126 down(&lock); 127 128 send_0_byte (dev); /* 0: LSB of frequency */ 129 130 for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ 131 if (frequency & (1 << i)) 132 send_1_byte (dev); 133 else 134 send_0_byte (dev); 135 136 send_0_byte (dev); /* 14: test bit - always 0 */ 137 send_0_byte (dev); /* 15: test bit - always 0 */ 138 send_0_byte (dev); /* 16: band data 0 - always 0 */ 139 if (dev->stereo) /* 17: stereo (1 to enable) */ 140 send_1_byte (dev); 141 else 142 send_0_byte (dev); 143 144 send_1_byte (dev); /* 18: band data 1 - unknown */ 145 send_0_byte (dev); /* 19: time base - always 0 */ 146 send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */ 147 send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */ 148 send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */ 149 send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */ 150 151 /* latch frequency */ 152 153 udelay (radio_wait_time); 154 outb_p(128+64+volconvert(dev->curvol), io); 155 156 up(&lock); 157 158 return 0; 159} 160 161static int az_ioctl(struct video_device *dev, unsigned int cmd, void *arg) 162{ 163 struct az_device *az=dev->priv; 164 165 switch(cmd) 166 { 167 case VIDIOCGCAP: 168 { 169 struct video_capability v; 170 v.type=VID_TYPE_TUNER; 171 v.channels=1; 172 v.audios=1; 173 /* No we don't do pictures */ 174 v.maxwidth=0; 175 v.maxheight=0; 176 v.minwidth=0; 177 v.minheight=0; 178 strcpy(v.name, "Aztech Radio"); 179 if(copy_to_user(arg,&v,sizeof(v))) 180 return -EFAULT; 181 return 0; 182 } 183 case VIDIOCGTUNER: 184 { 185 struct video_tuner v; 186 if(copy_from_user(&v, arg,sizeof(v))!=0) 187 return -EFAULT; 188 if(v.tuner) /* Only 1 tuner */ 189 return -EINVAL; 190 v.rangelow=(87*16000); 191 v.rangehigh=(108*16000); 192 v.flags=VIDEO_TUNER_LOW; 193 v.mode=VIDEO_MODE_AUTO; 194 v.signal=0xFFFF*az_getsigstr(az); 195 if(az_getstereo(az)) 196 v.flags|=VIDEO_TUNER_STEREO_ON; 197 strcpy(v.name, "FM"); 198 if(copy_to_user(arg,&v, sizeof(v))) 199 return -EFAULT; 200 return 0; 201 } 202 case VIDIOCSTUNER: 203 { 204 struct video_tuner v; 205 if(copy_from_user(&v, arg, sizeof(v))) 206 return -EFAULT; 207 if(v.tuner!=0) 208 return -EINVAL; 209 /* Only 1 tuner so no setting needed ! */ 210 return 0; 211 } 212 case VIDIOCGFREQ: 213 if(copy_to_user(arg, &az->curfreq, sizeof(az->curfreq))) 214 return -EFAULT; 215 return 0; 216 case VIDIOCSFREQ: 217 if(copy_from_user(&az->curfreq, arg,sizeof(az->curfreq))) 218 return -EFAULT; 219 az_setfreq(az, az->curfreq); 220 return 0; 221 case VIDIOCGAUDIO: 222 { 223 struct video_audio v; 224 memset(&v,0, sizeof(v)); 225 v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; 226 if(az->stereo) 227 v.mode=VIDEO_SOUND_STEREO; 228 else 229 v.mode=VIDEO_SOUND_MONO; 230 v.volume=az->curvol; 231 v.step=16384; 232 strcpy(v.name, "Radio"); 233 if(copy_to_user(arg,&v, sizeof(v))) 234 return -EFAULT; 235 return 0; 236 } 237 case VIDIOCSAUDIO: 238 { 239 struct video_audio v; 240 if(copy_from_user(&v, arg, sizeof(v))) 241 return -EFAULT; 242 if(v.audio) 243 return -EINVAL; 244 az->curvol=v.volume; 245 246 az->stereo=(v.mode&VIDEO_SOUND_STEREO)?1:0; 247 if(v.flags&VIDEO_AUDIO_MUTE) 248 az_setvol(az,0); 249 else 250 az_setvol(az,az->curvol); 251 return 0; 252 } 253 default: 254 return -ENOIOCTLCMD; 255 } 256} 257 258static int az_open(struct video_device *dev, int flags) 259{ 260 if(users) 261 return -EBUSY; 262 users++; 263 return 0; 264} 265 266static void az_close(struct video_device *dev) 267{ 268 users--; 269} 270 271static struct az_device aztech_unit; 272 273static struct video_device aztech_radio= 274{ 275 owner: THIS_MODULE, 276 name: "Aztech radio", 277 type: VID_TYPE_TUNER, 278 hardware: VID_HARDWARE_AZTECH, 279 open: az_open, 280 close: az_close, 281 ioctl: az_ioctl, 282}; 283 284static int __init aztech_init(void) 285{ 286 if(io==-1) 287 { 288 printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 289 return -EINVAL; 290 } 291 292 if (!request_region(io, 2, "aztech")) 293 { 294 printk(KERN_ERR "aztech: port 0x%x already in use\n", io); 295 return -EBUSY; 296 } 297 298 init_MUTEX(&lock); 299 aztech_radio.priv=&aztech_unit; 300 301 if(video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr)==-1) 302 { 303 release_region(io,2); 304 return -EINVAL; 305 } 306 307 printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); 308 /* mute card - prevents noisy bootups */ 309 outb (0, io); 310 return 0; 311} 312 313MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 314MODULE_DESCRIPTION("A driver for the Aztech radio card."); 315MODULE_LICENSE("GPL"); 316 317MODULE_PARM(io, "i"); 318MODULE_PARM(radio_nr, "i"); 319MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); 320 321EXPORT_NO_SYMBOLS; 322 323static void __exit aztech_cleanup(void) 324{ 325 video_unregister_device(&aztech_radio); 326 release_region(io,2); 327} 328 329module_init(aztech_init); 330module_exit(aztech_cleanup); 331