1 /* radio-trust.c - Trust FM Radio card driver for Linux 2.2 2 * by Eric Lammerts <eric@scintilla.utwente.nl> 3 * 4 * Based on radio-aztech.c. Original notes: 5 * 6 * Adapted to support the Video for Linux API by 7 * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: 8 * 9 * Quay Ly 10 * Donald Song 11 * Jason Lewis (jlewis@twilight.vtc.vsc.edu) 12 * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) 13 * William McGrath (wmcgrath@twilight.vtc.vsc.edu) 14 * 15 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 16 */ 17 18 #include <stdarg.h> 19 #include <linux/module.h> 20 #include <linux/init.h> 21 #include <linux/ioport.h> 22 #include <asm/io.h> 23 #include <asm/uaccess.h> 24 #include <linux/videodev2.h> 25 #include <media/v4l2-common.h> 26 27 #include <linux/version.h> /* for KERNEL_VERSION MACRO */ 28 #define RADIO_VERSION KERNEL_VERSION(0,0,2) 29 30 static struct v4l2_queryctrl radio_qctrl[] = { 31 { 32 .id = V4L2_CID_AUDIO_MUTE, 33 .name = "Mute", 34 .minimum = 0, 35 .maximum = 1, 36 .default_value = 1, 37 .type = V4L2_CTRL_TYPE_BOOLEAN, 38 },{ 39 .id = V4L2_CID_AUDIO_VOLUME, 40 .name = "Volume", 41 .minimum = 0, 42 .maximum = 65535, 43 .step = 2048, 44 .default_value = 65535, 45 .type = V4L2_CTRL_TYPE_INTEGER, 46 },{ 47 .id = V4L2_CID_AUDIO_BASS, 48 .name = "Bass", 49 .minimum = 0, 50 .maximum = 65535, 51 .step = 4370, 52 .default_value = 32768, 53 .type = V4L2_CTRL_TYPE_INTEGER, 54 },{ 55 .id = V4L2_CID_AUDIO_TREBLE, 56 .name = "Treble", 57 .minimum = 0, 58 .maximum = 65535, 59 .step = 4370, 60 .default_value = 32768, 61 .type = V4L2_CTRL_TYPE_INTEGER, 62 }, 63 }; 64 65 /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ 66 67 #ifndef CONFIG_RADIO_TRUST_PORT 68 #define CONFIG_RADIO_TRUST_PORT -1 69 #endif 70 71 static int io = CONFIG_RADIO_TRUST_PORT; 72 static int radio_nr = -1; 73 static int ioval = 0xf; 74 static __u16 curvol; 75 static __u16 curbass; 76 static __u16 curtreble; 77 static unsigned long curfreq; 78 static int curstereo; 79 static int curmute; 80 81 /* i2c addresses */ 82 #define TDA7318_ADDR 0x88 83 #define TSA6060T_ADDR 0xc4 84 85 #define TR_DELAY do { inb(io); inb(io); inb(io); } while(0) 86 #define TR_SET_SCL outb(ioval |= 2, io) 87 #define TR_CLR_SCL outb(ioval &= 0xfd, io) 88 #define TR_SET_SDA outb(ioval |= 1, io) 89 #define TR_CLR_SDA outb(ioval &= 0xfe, io) 90 91 static void write_i2c(int n, ...) 92 { 93 unsigned char val, mask; 94 va_list args; 95 96 va_start(args, n); 97 98 /* start condition */ 99 TR_SET_SDA; 100 TR_SET_SCL; 101 TR_DELAY; 102 TR_CLR_SDA; 103 TR_CLR_SCL; 104 TR_DELAY; 105 106 for(; n; n--) { 107 val = va_arg(args, unsigned); 108 for(mask = 0x80; mask; mask >>= 1) { 109 if(val & mask) 110 TR_SET_SDA; 111 else 112 TR_CLR_SDA; 113 TR_SET_SCL; 114 TR_DELAY; 115 TR_CLR_SCL; 116 TR_DELAY; 117 } 118 /* acknowledge bit */ 119 TR_SET_SDA; 120 TR_SET_SCL; 121 TR_DELAY; 122 TR_CLR_SCL; 123 TR_DELAY; 124 } 125 126 /* stop condition */ 127 TR_CLR_SDA; 128 TR_DELAY; 129 TR_SET_SCL; 130 TR_DELAY; 131 TR_SET_SDA; 132 TR_DELAY; 133 134 va_end(args); 135 } 136 137 static void tr_setvol(__u16 vol) 138 { 139 curvol = vol / 2048; 140 write_i2c(2, TDA7318_ADDR, curvol ^ 0x1f); 141 } 142 143 static int basstreble2chip[15] = { 144 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 145 }; 146 147 static void tr_setbass(__u16 bass) 148 { 149 curbass = bass / 4370; 150 write_i2c(2, TDA7318_ADDR, 0x60 | basstreble2chip[curbass]); 151 } 152 153 static void tr_settreble(__u16 treble) 154 { 155 curtreble = treble / 4370; 156 write_i2c(2, TDA7318_ADDR, 0x70 | basstreble2chip[curtreble]); 157 } 158 159 static void tr_setstereo(int stereo) 160 { 161 curstereo = !!stereo; 162 ioval = (ioval & 0xfb) | (!curstereo << 2); 163 outb(ioval, io); 164 } 165 166 static void tr_setmute(int mute) 167 { 168 curmute = !!mute; 169 ioval = (ioval & 0xf7) | (curmute << 3); 170 outb(ioval, io); 171 } 172 173 static int tr_getsigstr(void) 174 { 175 int i, v; 176 177 for(i = 0, v = 0; i < 100; i++) v |= inb(io); 178 return (v & 1)? 0 : 0xffff; 179 } 180 181 static int tr_getstereo(void) 182 { 183 /* don't know how to determine it, just return the setting */ 184 return curstereo; 185 } 186 187 static void tr_setfreq(unsigned long f) 188 { 189 f /= 160; /* Convert to 10 kHz units */ 190 f += 1070; /* Add 10.7 MHz IF */ 191 192 write_i2c(5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0); 193 } 194 195 static int vidioc_querycap(struct file *file, void *priv, 196 struct v4l2_capability *v) 197 { 198 strlcpy(v->driver, "radio-trust", sizeof(v->driver)); 199 strlcpy(v->card, "Trust FM Radio", sizeof(v->card)); 200 sprintf(v->bus_info, "ISA"); 201 v->version = RADIO_VERSION; 202 v->capabilities = V4L2_CAP_TUNER; 203 return 0; 204 } 205 206 static int vidioc_g_tuner(struct file *file, void *priv, 207 struct v4l2_tuner *v) 208 { 209 if (v->index > 0) 210 return -EINVAL; 211 212 strcpy(v->name, "FM"); 213 v->type = V4L2_TUNER_RADIO; 214 v->rangelow = (87.5*16000); 215 v->rangehigh = (108*16000); 216 v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO; 217 v->capability = V4L2_TUNER_CAP_LOW; 218 if (tr_getstereo()) 219 v->audmode = V4L2_TUNER_MODE_STEREO; 220 else 221 v->audmode = V4L2_TUNER_MODE_MONO; 222 v->signal = tr_getsigstr(); 223 return 0; 224 } 225 226 static int vidioc_s_tuner(struct file *file, void *priv, 227 struct v4l2_tuner *v) 228 { 229 if (v->index > 0) 230 return -EINVAL; 231 232 return 0; 233 } 234 235 static int vidioc_s_frequency(struct file *file, void *priv, 236 struct v4l2_frequency *f) 237 { 238 curfreq = f->frequency; 239 tr_setfreq(curfreq); 240 return 0; 241 } 242 243 static int vidioc_g_frequency(struct file *file, void *priv, 244 struct v4l2_frequency *f) 245 { 246 f->type = V4L2_TUNER_RADIO; 247 f->frequency = curfreq; 248 return 0; 249 } 250 251 static int vidioc_queryctrl(struct file *file, void *priv, 252 struct v4l2_queryctrl *qc) 253 { 254 int i; 255 256 for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 257 if (qc->id && qc->id == radio_qctrl[i].id) { 258 memcpy(qc, &(radio_qctrl[i]), 259 sizeof(*qc)); 260 return 0; 261 } 262 } 263 return -EINVAL; 264 } 265 266 static int vidioc_g_ctrl(struct file *file, void *priv, 267 struct v4l2_control *ctrl) 268 { 269 switch (ctrl->id) { 270 case V4L2_CID_AUDIO_MUTE: 271 ctrl->value = curmute; 272 return 0; 273 case V4L2_CID_AUDIO_VOLUME: 274 ctrl->value = curvol * 2048; 275 return 0; 276 case V4L2_CID_AUDIO_BASS: 277 ctrl->value = curbass * 4370; 278 return 0; 279 case V4L2_CID_AUDIO_TREBLE: 280 ctrl->value = curtreble * 4370; 281 return 0; 282 } 283 return -EINVAL; 284 } 285 286 static int vidioc_s_ctrl(struct file *file, void *priv, 287 struct v4l2_control *ctrl) 288 { 289 switch (ctrl->id) { 290 case V4L2_CID_AUDIO_MUTE: 291 tr_setmute(ctrl->value); 292 return 0; 293 case V4L2_CID_AUDIO_VOLUME: 294 tr_setvol(ctrl->value); 295 return 0; 296 case V4L2_CID_AUDIO_BASS: 297 tr_setbass(ctrl->value); 298 return 0; 299 case V4L2_CID_AUDIO_TREBLE: 300 tr_settreble(ctrl->value); 301 return 0; 302 } 303 return -EINVAL; 304 } 305 306 static int vidioc_g_audio(struct file *file, void *priv, 307 struct v4l2_audio *a) 308 { 309 if (a->index > 1) 310 return -EINVAL; 311 312 strcpy(a->name, "Radio"); 313 a->capability = V4L2_AUDCAP_STEREO; 314 return 0; 315 } 316 317 static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 318 { 319 *i = 0; 320 return 0; 321 } 322 323 static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 324 { 325 if (i != 0) 326 return -EINVAL; 327 return 0; 328 } 329 330 static int vidioc_s_audio(struct file *file, void *priv, 331 struct v4l2_audio *a) 332 { 333 if (a->index != 0) 334 return -EINVAL; 335 return 0; 336 } 337 338 static const struct file_operations trust_fops = { 339 .owner = THIS_MODULE, 340 .open = video_exclusive_open, 341 .release = video_exclusive_release, 342 .ioctl = video_ioctl2, 343 .compat_ioctl = v4l_compat_ioctl32, 344 .llseek = no_llseek, 345 }; 346 347 static struct video_device trust_radio= 348 { 349 .owner = THIS_MODULE, 350 .name = "Trust FM Radio", 351 .type = VID_TYPE_TUNER, 352 .fops = &trust_fops, 353 .vidioc_querycap = vidioc_querycap, 354 .vidioc_g_tuner = vidioc_g_tuner, 355 .vidioc_s_tuner = vidioc_s_tuner, 356 .vidioc_g_frequency = vidioc_g_frequency, 357 .vidioc_s_frequency = vidioc_s_frequency, 358 .vidioc_queryctrl = vidioc_queryctrl, 359 .vidioc_g_ctrl = vidioc_g_ctrl, 360 .vidioc_s_ctrl = vidioc_s_ctrl, 361 .vidioc_g_audio = vidioc_g_audio, 362 .vidioc_s_audio = vidioc_s_audio, 363 .vidioc_g_input = vidioc_g_input, 364 .vidioc_s_input = vidioc_s_input, 365 }; 366 367 static int __init trust_init(void) 368 { 369 if(io == -1) { 370 printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 371 return -EINVAL; 372 } 373 if(!request_region(io, 2, "Trust FM Radio")) { 374 printk(KERN_ERR "trust: port 0x%x already in use\n", io); 375 return -EBUSY; 376 } 377 if(video_register_device(&trust_radio, VFL_TYPE_RADIO, radio_nr)==-1) 378 { 379 release_region(io, 2); 380 return -EINVAL; 381 } 382 383 printk(KERN_INFO "Trust FM Radio card driver v1.0.\n"); 384 385 write_i2c(2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ 386 write_i2c(2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ 387 write_i2c(2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ 388 write_i2c(2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ 389 write_i2c(2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ 390 391 tr_setvol(0x8000); 392 tr_setbass(0x8000); 393 tr_settreble(0x8000); 394 tr_setstereo(1); 395 396 /* mute card - prevents noisy bootups */ 397 tr_setmute(1); 398 399 return 0; 400 } 401 402 MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 403 MODULE_DESCRIPTION("A driver for the Trust FM Radio card."); 404 MODULE_LICENSE("GPL"); 405 406 module_param(io, int, 0); 407 MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); 408 module_param(radio_nr, int, 0); 409 410 static void __exit cleanup_trust_module(void) 411 { 412 video_unregister_device(&trust_radio); 413 release_region(io, 2); 414 } 415 416 module_init(trust_init); 417 module_exit(cleanup_trust_module); 418