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 30static 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 71static int io = CONFIG_RADIO_TRUST_PORT; 72static int radio_nr = -1; 73static int ioval = 0xf; 74static __u16 curvol; 75static __u16 curbass; 76static __u16 curtreble; 77static unsigned long curfreq; 78static int curstereo; 79static 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 91static 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 137static void tr_setvol(__u16 vol) 138{ 139 curvol = vol / 2048; 140 write_i2c(2, TDA7318_ADDR, curvol ^ 0x1f); 141} 142 143static int basstreble2chip[15] = { 144 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 145}; 146 147static void tr_setbass(__u16 bass) 148{ 149 curbass = bass / 4370; 150 write_i2c(2, TDA7318_ADDR, 0x60 | basstreble2chip[curbass]); 151} 152 153static void tr_settreble(__u16 treble) 154{ 155 curtreble = treble / 4370; 156 write_i2c(2, TDA7318_ADDR, 0x70 | basstreble2chip[curtreble]); 157} 158 159static void tr_setstereo(int stereo) 160{ 161 curstereo = !!stereo; 162 ioval = (ioval & 0xfb) | (!curstereo << 2); 163 outb(ioval, io); 164} 165 166static void tr_setmute(int mute) 167{ 168 curmute = !!mute; 169 ioval = (ioval & 0xf7) | (curmute << 3); 170 outb(ioval, io); 171} 172 173static 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 181static int tr_getstereo(void) 182{ 183 /* don't know how to determine it, just return the setting */ 184 return curstereo; 185} 186 187static 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 195static 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 206static 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 226static 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 235static 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 243static 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 251static 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 266static 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 286static 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 306static 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 317static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 318{ 319 *i = 0; 320 return 0; 321} 322 323static 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 330static 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 338static 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 347static struct video_device trust_radio= 348{ 349 .owner = THIS_MODULE, 350 .name = "Trust FM Radio", 351 .type = VID_TYPE_TUNER, 352 .hardware = 0, 353 .fops = &trust_fops, 354 .vidioc_querycap = vidioc_querycap, 355 .vidioc_g_tuner = vidioc_g_tuner, 356 .vidioc_s_tuner = vidioc_s_tuner, 357 .vidioc_g_frequency = vidioc_g_frequency, 358 .vidioc_s_frequency = vidioc_s_frequency, 359 .vidioc_queryctrl = vidioc_queryctrl, 360 .vidioc_g_ctrl = vidioc_g_ctrl, 361 .vidioc_s_ctrl = vidioc_s_ctrl, 362 .vidioc_g_audio = vidioc_g_audio, 363 .vidioc_s_audio = vidioc_s_audio, 364 .vidioc_g_input = vidioc_g_input, 365 .vidioc_s_input = vidioc_s_input, 366}; 367 368static int __init trust_init(void) 369{ 370 if(io == -1) { 371 printk(KERN_ERR "You must set an I/O address with io=0x???\n"); 372 return -EINVAL; 373 } 374 if(!request_region(io, 2, "Trust FM Radio")) { 375 printk(KERN_ERR "trust: port 0x%x already in use\n", io); 376 return -EBUSY; 377 } 378 if(video_register_device(&trust_radio, VFL_TYPE_RADIO, radio_nr)==-1) 379 { 380 release_region(io, 2); 381 return -EINVAL; 382 } 383 384 printk(KERN_INFO "Trust FM Radio card driver v1.0.\n"); 385 386 write_i2c(2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ 387 write_i2c(2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ 388 write_i2c(2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ 389 write_i2c(2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ 390 write_i2c(2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ 391 392 tr_setvol(0x8000); 393 tr_setbass(0x8000); 394 tr_settreble(0x8000); 395 tr_setstereo(1); 396 397 /* mute card - prevents noisy bootups */ 398 tr_setmute(1); 399 400 return 0; 401} 402 403MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 404MODULE_DESCRIPTION("A driver for the Trust FM Radio card."); 405MODULE_LICENSE("GPL"); 406 407module_param(io, int, 0); 408MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); 409module_param(radio_nr, int, 0); 410 411static void __exit cleanup_trust_module(void) 412{ 413 video_unregister_device(&trust_radio); 414 release_region(io, 2); 415} 416 417module_init(trust_init); 418module_exit(cleanup_trust_module); 419