13088fba8SHans Verkuil /* 23088fba8SHans Verkuil * radio-aztech.c - Aztech radio card driver 31da177e4SLinus Torvalds * 43088fba8SHans Verkuil * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl> 5a4366af4SMauro Carvalho Chehab * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 61da177e4SLinus Torvalds * Adapted to support the Video for Linux API by 71da177e4SLinus Torvalds * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: 81da177e4SLinus Torvalds * 91da177e4SLinus Torvalds * Quay Ly 101da177e4SLinus Torvalds * Donald Song 111da177e4SLinus Torvalds * Jason Lewis (jlewis@twilight.vtc.vsc.edu) 121da177e4SLinus Torvalds * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) 131da177e4SLinus Torvalds * William McGrath (wmcgrath@twilight.vtc.vsc.edu) 141da177e4SLinus Torvalds * 153088fba8SHans Verkuil * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. 161da177e4SLinus Torvalds */ 171da177e4SLinus Torvalds 181da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 191da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 20fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 211da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 22a4366af4SMauro Carvalho Chehab #include <linux/videodev2.h> /* kernel radio structs */ 23e697e12eSHans Verkuil #include <linux/io.h> /* outb, outb_p */ 249f1dfccfSHans Verkuil #include <linux/slab.h> 25e697e12eSHans Verkuil #include <media/v4l2-device.h> 2635ea11ffSHans Verkuil #include <media/v4l2-ioctl.h> 273088fba8SHans Verkuil #include <media/v4l2-ctrls.h> 283088fba8SHans Verkuil #include "radio-isa.h" 291da177e4SLinus Torvalds 30e697e12eSHans Verkuil MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); 31e697e12eSHans Verkuil MODULE_DESCRIPTION("A driver for the Aztech radio card."); 32e697e12eSHans Verkuil MODULE_LICENSE("GPL"); 333088fba8SHans Verkuil MODULE_VERSION("1.0.0"); 34a4366af4SMauro Carvalho Chehab 351da177e4SLinus Torvalds /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ 361da177e4SLinus Torvalds #ifndef CONFIG_RADIO_AZTECH_PORT 371da177e4SLinus Torvalds #define CONFIG_RADIO_AZTECH_PORT -1 381da177e4SLinus Torvalds #endif 391da177e4SLinus Torvalds 403088fba8SHans Verkuil #define AZTECH_MAX 2 411da177e4SLinus Torvalds 423088fba8SHans Verkuil static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT, 433088fba8SHans Verkuil [1 ... (AZTECH_MAX - 1)] = -1 }; 443088fba8SHans Verkuil static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 }; 453088fba8SHans Verkuil static const int radio_wait_time = 1000; 46e697e12eSHans Verkuil 473088fba8SHans Verkuil module_param_array(io, int, NULL, 0444); 483088fba8SHans Verkuil MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)"); 493088fba8SHans Verkuil module_param_array(radio_nr, int, NULL, 0444); 503088fba8SHans Verkuil MODULE_PARM_DESC(radio_nr, "Radio device numbers"); 513088fba8SHans Verkuil 523088fba8SHans Verkuil struct aztech { 533088fba8SHans Verkuil struct radio_isa_card isa; 541da177e4SLinus Torvalds int curvol; 551da177e4SLinus Torvalds }; 561da177e4SLinus Torvalds 57e697e12eSHans Verkuil static void send_0_byte(struct aztech *az) 581da177e4SLinus Torvalds { 591da177e4SLinus Torvalds udelay(radio_wait_time); 603088fba8SHans Verkuil outb_p(2 + az->curvol, az->isa.io); 613088fba8SHans Verkuil outb_p(64 + 2 + az->curvol, az->isa.io); 621da177e4SLinus Torvalds } 631da177e4SLinus Torvalds 64e697e12eSHans Verkuil static void send_1_byte(struct aztech *az) 651da177e4SLinus Torvalds { 661da177e4SLinus Torvalds udelay(radio_wait_time); 673088fba8SHans Verkuil outb_p(128 + 2 + az->curvol, az->isa.io); 683088fba8SHans Verkuil outb_p(128 + 64 + 2 + az->curvol, az->isa.io); 691da177e4SLinus Torvalds } 701da177e4SLinus Torvalds 713088fba8SHans Verkuil static struct radio_isa_card *aztech_alloc(void) 721da177e4SLinus Torvalds { 733088fba8SHans Verkuil struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL); 743088fba8SHans Verkuil 753088fba8SHans Verkuil return az ? &az->isa : NULL; 761da177e4SLinus Torvalds } 771da177e4SLinus Torvalds 783088fba8SHans Verkuil static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq) 791da177e4SLinus Torvalds { 803088fba8SHans Verkuil struct aztech *az = container_of(isa, struct aztech, isa); 811da177e4SLinus Torvalds int i; 821da177e4SLinus Torvalds 833088fba8SHans Verkuil freq += 171200; /* Add 10.7 MHz IF */ 843088fba8SHans Verkuil freq /= 800; /* Convert to 50 kHz units */ 851da177e4SLinus Torvalds 86e697e12eSHans Verkuil send_0_byte(az); /* 0: LSB of frequency */ 871da177e4SLinus Torvalds 881da177e4SLinus Torvalds for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ 893088fba8SHans Verkuil if (freq & (1 << i)) 90e697e12eSHans Verkuil send_1_byte(az); 911da177e4SLinus Torvalds else 92e697e12eSHans Verkuil send_0_byte(az); 931da177e4SLinus Torvalds 94e697e12eSHans Verkuil send_0_byte(az); /* 14: test bit - always 0 */ 95e697e12eSHans Verkuil send_0_byte(az); /* 15: test bit - always 0 */ 96e697e12eSHans Verkuil send_0_byte(az); /* 16: band data 0 - always 0 */ 973088fba8SHans Verkuil if (isa->stereo) /* 17: stereo (1 to enable) */ 98e697e12eSHans Verkuil send_1_byte(az); 991da177e4SLinus Torvalds else 100e697e12eSHans Verkuil send_0_byte(az); 1011da177e4SLinus Torvalds 102e697e12eSHans Verkuil send_1_byte(az); /* 18: band data 1 - unknown */ 103e697e12eSHans Verkuil send_0_byte(az); /* 19: time base - always 0 */ 104e697e12eSHans Verkuil send_0_byte(az); /* 20: spacing (0 = 25 kHz) */ 105e697e12eSHans Verkuil send_1_byte(az); /* 21: spacing (1 = 25 kHz) */ 106e697e12eSHans Verkuil send_0_byte(az); /* 22: spacing (0 = 25 kHz) */ 107e697e12eSHans Verkuil send_1_byte(az); /* 23: AM/FM (FM = 1, always) */ 1081da177e4SLinus Torvalds 1091da177e4SLinus Torvalds /* latch frequency */ 1101da177e4SLinus Torvalds 1111da177e4SLinus Torvalds udelay(radio_wait_time); 1123088fba8SHans Verkuil outb_p(128 + 64 + az->curvol, az->isa.io); 1131da177e4SLinus Torvalds 1141da177e4SLinus Torvalds return 0; 1151da177e4SLinus Torvalds } 1161da177e4SLinus Torvalds 1173088fba8SHans Verkuil /* thanks to Michael Dwyer for giving me a dose of clues in 1183088fba8SHans Verkuil * the signal strength department.. 1193088fba8SHans Verkuil * 1203088fba8SHans Verkuil * This card has a stereo bit - bit 0 set = mono, not set = stereo 1213088fba8SHans Verkuil */ 1223088fba8SHans Verkuil static u32 aztech_g_rxsubchans(struct radio_isa_card *isa) 1231da177e4SLinus Torvalds { 1243088fba8SHans Verkuil if (inb(isa->io) & 1) 1253088fba8SHans Verkuil return V4L2_TUNER_SUB_MONO; 1263088fba8SHans Verkuil return V4L2_TUNER_SUB_STEREO; 1273088fba8SHans Verkuil } 1283088fba8SHans Verkuil 1293088fba8SHans Verkuil static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo) 1303088fba8SHans Verkuil { 1313088fba8SHans Verkuil return aztech_s_frequency(isa, isa->freq); 1323088fba8SHans Verkuil } 1333088fba8SHans Verkuil 1343088fba8SHans Verkuil static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 1353088fba8SHans Verkuil { 1363088fba8SHans Verkuil struct aztech *az = container_of(isa, struct aztech, isa); 1373088fba8SHans Verkuil 1383088fba8SHans Verkuil if (mute) 1393088fba8SHans Verkuil vol = 0; 1403088fba8SHans Verkuil az->curvol = (vol & 1) + ((vol & 2) << 1); 1413088fba8SHans Verkuil outb(az->curvol, isa->io); 1421da177e4SLinus Torvalds return 0; 1431da177e4SLinus Torvalds } 14499218fe4SMauro Carvalho Chehab 1453088fba8SHans Verkuil static const struct radio_isa_ops aztech_ops = { 1463088fba8SHans Verkuil .alloc = aztech_alloc, 1473088fba8SHans Verkuil .s_mute_volume = aztech_s_mute_volume, 1483088fba8SHans Verkuil .s_frequency = aztech_s_frequency, 1493088fba8SHans Verkuil .s_stereo = aztech_s_stereo, 1503088fba8SHans Verkuil .g_rxsubchans = aztech_g_rxsubchans, 1511da177e4SLinus Torvalds }; 1521da177e4SLinus Torvalds 1533088fba8SHans Verkuil static const int aztech_ioports[] = { 0x350, 0x358 }; 1543088fba8SHans Verkuil 1553088fba8SHans Verkuil static struct radio_isa_driver aztech_driver = { 1563088fba8SHans Verkuil .driver = { 1573088fba8SHans Verkuil .match = radio_isa_match, 1583088fba8SHans Verkuil .probe = radio_isa_probe, 1593088fba8SHans Verkuil .remove = radio_isa_remove, 1603088fba8SHans Verkuil .driver = { 1613088fba8SHans Verkuil .name = "radio-aztech", 1623088fba8SHans Verkuil }, 1633088fba8SHans Verkuil }, 1643088fba8SHans Verkuil .io_params = io, 1653088fba8SHans Verkuil .radio_nr_params = radio_nr, 1663088fba8SHans Verkuil .io_ports = aztech_ioports, 1673088fba8SHans Verkuil .num_of_io_ports = ARRAY_SIZE(aztech_ioports), 1683088fba8SHans Verkuil .region_size = 2, 1693088fba8SHans Verkuil .card = "Aztech Radio", 1703088fba8SHans Verkuil .ops = &aztech_ops, 1713088fba8SHans Verkuil .has_stereo = true, 1723088fba8SHans Verkuil .max_volume = 3, 1731da177e4SLinus Torvalds }; 1741da177e4SLinus Torvalds 1751da177e4SLinus Torvalds static int __init aztech_init(void) 1761da177e4SLinus Torvalds { 1773088fba8SHans Verkuil return isa_register_driver(&aztech_driver.driver, AZTECH_MAX); 1781da177e4SLinus Torvalds } 1791da177e4SLinus Torvalds 180e697e12eSHans Verkuil static void __exit aztech_exit(void) 1811da177e4SLinus Torvalds { 1823088fba8SHans Verkuil isa_unregister_driver(&aztech_driver.driver); 1831da177e4SLinus Torvalds } 1841da177e4SLinus Torvalds 1851da177e4SLinus Torvalds module_init(aztech_init); 186e697e12eSHans Verkuil module_exit(aztech_exit); 187