1 /* Terratec ActiveRadio ISA Standalone card driver for Linux radio support 2 * (c) 1999 R. Offermanns (rolf@offermanns.de) 3 * based on the aimslab radio driver from M. Kirkwood 4 * many thanks to Michael Becker and Friedhelm Birth (from TerraTec) 5 * 6 * 7 * History: 8 * 1999-05-21 First preview release 9 * 10 * Notes on the hardware: 11 * There are two "main" chips on the card: 12 * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf) 13 * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf) 14 * (you can get the datasheet at the above links) 15 * 16 * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 17 * Volume Control is done digitally 18 * 19 * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday 20 * (as soon i have understand how to get started :) 21 * If you can help me out with that, please contact me!! 22 * 23 * 24 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 25 */ 26 27 #include <linux/module.h> /* Modules */ 28 #include <linux/init.h> /* Initdata */ 29 #include <linux/ioport.h> /* request_region */ 30 #include <linux/videodev2.h> /* kernel radio structs */ 31 #include <linux/mutex.h> 32 #include <linux/io.h> /* outb, outb_p */ 33 #include <media/v4l2-device.h> 34 #include <media/v4l2-ioctl.h> 35 36 MODULE_AUTHOR("R.OFFERMANNS & others"); 37 MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); 38 MODULE_LICENSE("GPL"); 39 MODULE_VERSION("0.0.3"); 40 41 #ifndef CONFIG_RADIO_TERRATEC_PORT 42 #define CONFIG_RADIO_TERRATEC_PORT 0x590 43 #endif 44 45 static int io = CONFIG_RADIO_TERRATEC_PORT; 46 static int radio_nr = -1; 47 48 module_param(io, int, 0); 49 MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); 50 module_param(radio_nr, int, 0); 51 52 static struct v4l2_queryctrl radio_qctrl[] = { 53 { 54 .id = V4L2_CID_AUDIO_MUTE, 55 .name = "Mute", 56 .minimum = 0, 57 .maximum = 1, 58 .default_value = 1, 59 .type = V4L2_CTRL_TYPE_BOOLEAN, 60 },{ 61 .id = V4L2_CID_AUDIO_VOLUME, 62 .name = "Volume", 63 .minimum = 0, 64 .maximum = 0xff, 65 .step = 1, 66 .default_value = 0xff, 67 .type = V4L2_CTRL_TYPE_INTEGER, 68 } 69 }; 70 71 #define WRT_DIS 0x00 72 #define CLK_OFF 0x00 73 #define IIC_DATA 0x01 74 #define IIC_CLK 0x02 75 #define DATA 0x04 76 #define CLK_ON 0x08 77 #define WRT_EN 0x10 78 79 struct terratec 80 { 81 struct v4l2_device v4l2_dev; 82 struct video_device vdev; 83 int io; 84 int curvol; 85 unsigned long curfreq; 86 int muted; 87 struct mutex lock; 88 }; 89 90 static struct terratec terratec_card; 91 92 /* local things */ 93 94 static void tt_write_vol(struct terratec *tt, int volume) 95 { 96 int i; 97 98 volume = volume + (volume * 32); /* change both channels */ 99 mutex_lock(&tt->lock); 100 for (i = 0; i < 8; i++) { 101 if (volume & (0x80 >> i)) 102 outb(0x80, tt->io + 1); 103 else 104 outb(0x00, tt->io + 1); 105 } 106 mutex_unlock(&tt->lock); 107 } 108 109 110 111 static void tt_mute(struct terratec *tt) 112 { 113 tt->muted = 1; 114 tt_write_vol(tt, 0); 115 } 116 117 static int tt_setvol(struct terratec *tt, int vol) 118 { 119 if (vol == tt->curvol) { /* requested volume = current */ 120 if (tt->muted) { /* user is unmuting the card */ 121 tt->muted = 0; 122 tt_write_vol(tt, vol); /* enable card */ 123 } 124 return 0; 125 } 126 127 if (vol == 0) { /* volume = 0 means mute the card */ 128 tt_write_vol(tt, 0); /* "turn off card" by setting vol to 0 */ 129 tt->curvol = vol; /* track the volume state! */ 130 return 0; 131 } 132 133 tt->muted = 0; 134 tt_write_vol(tt, vol); 135 tt->curvol = vol; 136 return 0; 137 } 138 139 140 /* this is the worst part in this driver */ 141 /* many more or less strange things are going on here, but hey, it works :) */ 142 143 static int tt_setfreq(struct terratec *tt, unsigned long freq1) 144 { 145 int freq; 146 int i; 147 int p; 148 int temp; 149 long rest; 150 unsigned char buffer[25]; /* we have to bit shift 25 registers */ 151 152 mutex_lock(&tt->lock); 153 154 tt->curfreq = freq1; 155 156 freq = freq1 / 160; /* convert the freq. to a nice to handle value */ 157 memset(buffer, 0, sizeof(buffer)); 158 159 rest = freq * 10 + 10700; /* I once had understood what is going on here */ 160 /* maybe some wise guy (friedhelm?) can comment this stuff */ 161 i = 13; 162 p = 10; 163 temp = 102400; 164 while (rest != 0) { 165 if (rest % temp == rest) 166 buffer[i] = 0; 167 else { 168 buffer[i] = 1; 169 rest = rest - temp; 170 } 171 i--; 172 p--; 173 temp = temp / 2; 174 } 175 176 for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */ 177 if (buffer[i] == 1) { 178 outb(WRT_EN | DATA, tt->io); 179 outb(WRT_EN | DATA | CLK_ON, tt->io); 180 outb(WRT_EN | DATA, tt->io); 181 } else { 182 outb(WRT_EN | 0x00, tt->io); 183 outb(WRT_EN | 0x00 | CLK_ON, tt->io); 184 } 185 } 186 outb(0x00, tt->io); 187 188 mutex_unlock(&tt->lock); 189 190 return 0; 191 } 192 193 static int tt_getsigstr(struct terratec *tt) 194 { 195 if (inb(tt->io) & 2) /* bit set = no signal present */ 196 return 0; 197 return 1; /* signal present */ 198 } 199 200 static int vidioc_querycap(struct file *file, void *priv, 201 struct v4l2_capability *v) 202 { 203 strlcpy(v->driver, "radio-terratec", sizeof(v->driver)); 204 strlcpy(v->card, "ActiveRadio", sizeof(v->card)); 205 strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); 206 v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 207 return 0; 208 } 209 210 static int vidioc_g_tuner(struct file *file, void *priv, 211 struct v4l2_tuner *v) 212 { 213 struct terratec *tt = video_drvdata(file); 214 215 if (v->index > 0) 216 return -EINVAL; 217 218 strlcpy(v->name, "FM", sizeof(v->name)); 219 v->type = V4L2_TUNER_RADIO; 220 v->rangelow = 87 * 16000; 221 v->rangehigh = 108 * 16000; 222 v->rxsubchans = V4L2_TUNER_SUB_MONO; 223 v->capability = V4L2_TUNER_CAP_LOW; 224 v->audmode = V4L2_TUNER_MODE_MONO; 225 v->signal = 0xFFFF * tt_getsigstr(tt); 226 return 0; 227 } 228 229 static int vidioc_s_tuner(struct file *file, void *priv, 230 struct v4l2_tuner *v) 231 { 232 return v->index ? -EINVAL : 0; 233 } 234 235 static int vidioc_s_frequency(struct file *file, void *priv, 236 struct v4l2_frequency *f) 237 { 238 struct terratec *tt = video_drvdata(file); 239 240 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 241 return -EINVAL; 242 tt_setfreq(tt, f->frequency); 243 return 0; 244 } 245 246 static int vidioc_g_frequency(struct file *file, void *priv, 247 struct v4l2_frequency *f) 248 { 249 struct terratec *tt = video_drvdata(file); 250 251 if (f->tuner != 0) 252 return -EINVAL; 253 f->type = V4L2_TUNER_RADIO; 254 f->frequency = tt->curfreq; 255 return 0; 256 } 257 258 static int vidioc_queryctrl(struct file *file, void *priv, 259 struct v4l2_queryctrl *qc) 260 { 261 int i; 262 263 for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 264 if (qc->id && qc->id == radio_qctrl[i].id) { 265 memcpy(qc, &(radio_qctrl[i]), sizeof(*qc)); 266 return 0; 267 } 268 } 269 return -EINVAL; 270 } 271 272 static int vidioc_g_ctrl(struct file *file, void *priv, 273 struct v4l2_control *ctrl) 274 { 275 struct terratec *tt = video_drvdata(file); 276 277 switch (ctrl->id) { 278 case V4L2_CID_AUDIO_MUTE: 279 if (tt->muted) 280 ctrl->value = 1; 281 else 282 ctrl->value = 0; 283 return 0; 284 case V4L2_CID_AUDIO_VOLUME: 285 ctrl->value = tt->curvol * 6554; 286 return 0; 287 } 288 return -EINVAL; 289 } 290 291 static int vidioc_s_ctrl(struct file *file, void *priv, 292 struct v4l2_control *ctrl) 293 { 294 struct terratec *tt = video_drvdata(file); 295 296 switch (ctrl->id) { 297 case V4L2_CID_AUDIO_MUTE: 298 if (ctrl->value) 299 tt_mute(tt); 300 else 301 tt_setvol(tt,tt->curvol); 302 return 0; 303 case V4L2_CID_AUDIO_VOLUME: 304 tt_setvol(tt,ctrl->value); 305 return 0; 306 } 307 return -EINVAL; 308 } 309 310 static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 311 { 312 *i = 0; 313 return 0; 314 } 315 316 static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 317 { 318 return i ? -EINVAL : 0; 319 } 320 321 static int vidioc_g_audio(struct file *file, void *priv, 322 struct v4l2_audio *a) 323 { 324 a->index = 0; 325 strlcpy(a->name, "Radio", sizeof(a->name)); 326 a->capability = V4L2_AUDCAP_STEREO; 327 return 0; 328 } 329 330 static int vidioc_s_audio(struct file *file, void *priv, 331 struct v4l2_audio *a) 332 { 333 return a->index ? -EINVAL : 0; 334 } 335 336 static const struct v4l2_file_operations terratec_fops = { 337 .owner = THIS_MODULE, 338 .unlocked_ioctl = video_ioctl2, 339 }; 340 341 static const struct v4l2_ioctl_ops terratec_ioctl_ops = { 342 .vidioc_querycap = vidioc_querycap, 343 .vidioc_g_tuner = vidioc_g_tuner, 344 .vidioc_s_tuner = vidioc_s_tuner, 345 .vidioc_g_frequency = vidioc_g_frequency, 346 .vidioc_s_frequency = vidioc_s_frequency, 347 .vidioc_queryctrl = vidioc_queryctrl, 348 .vidioc_g_ctrl = vidioc_g_ctrl, 349 .vidioc_s_ctrl = vidioc_s_ctrl, 350 .vidioc_g_audio = vidioc_g_audio, 351 .vidioc_s_audio = vidioc_s_audio, 352 .vidioc_g_input = vidioc_g_input, 353 .vidioc_s_input = vidioc_s_input, 354 }; 355 356 static int __init terratec_init(void) 357 { 358 struct terratec *tt = &terratec_card; 359 struct v4l2_device *v4l2_dev = &tt->v4l2_dev; 360 int res; 361 362 strlcpy(v4l2_dev->name, "terratec", sizeof(v4l2_dev->name)); 363 tt->io = io; 364 if (tt->io == -1) { 365 v4l2_err(v4l2_dev, "you must set an I/O address with io=0x590 or 0x591\n"); 366 return -EINVAL; 367 } 368 if (!request_region(tt->io, 2, "terratec")) { 369 v4l2_err(v4l2_dev, "port 0x%x already in use\n", io); 370 return -EBUSY; 371 } 372 373 res = v4l2_device_register(NULL, v4l2_dev); 374 if (res < 0) { 375 release_region(tt->io, 2); 376 v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); 377 return res; 378 } 379 380 strlcpy(tt->vdev.name, v4l2_dev->name, sizeof(tt->vdev.name)); 381 tt->vdev.v4l2_dev = v4l2_dev; 382 tt->vdev.fops = &terratec_fops; 383 tt->vdev.ioctl_ops = &terratec_ioctl_ops; 384 tt->vdev.release = video_device_release_empty; 385 video_set_drvdata(&tt->vdev, tt); 386 387 mutex_init(&tt->lock); 388 389 /* mute card - prevents noisy bootups */ 390 tt_write_vol(tt, 0); 391 392 if (video_register_device(&tt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { 393 v4l2_device_unregister(&tt->v4l2_dev); 394 release_region(tt->io, 2); 395 return -EINVAL; 396 } 397 398 v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver.\n"); 399 return 0; 400 } 401 402 static void __exit terratec_exit(void) 403 { 404 struct terratec *tt = &terratec_card; 405 struct v4l2_device *v4l2_dev = &tt->v4l2_dev; 406 407 video_unregister_device(&tt->vdev); 408 v4l2_device_unregister(&tt->v4l2_dev); 409 release_region(tt->io, 2); 410 v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver unloaded.\n"); 411 } 412 413 module_init(terratec_init); 414 module_exit(terratec_exit); 415 416