1f8c08524SHans Verkuil /* 2f8c08524SHans Verkuil * GemTek radio card driver 3f8c08524SHans Verkuil * 4f8c08524SHans Verkuil * Copyright 1998 Jonas Munsin <jmunsin@iki.fi> 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * GemTek hasn't released any specs on the card, so the protocol had to 71da177e4SLinus Torvalds * be reverse engineered with dosemu. 81da177e4SLinus Torvalds * 91da177e4SLinus Torvalds * Besides the protocol changes, this is mostly a copy of: 101da177e4SLinus Torvalds * 111da177e4SLinus Torvalds * RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff 121da177e4SLinus Torvalds * 131da177e4SLinus Torvalds * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood 14d9b01449SAlan Cox * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> 151da177e4SLinus Torvalds * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> 161da177e4SLinus Torvalds * 17f8c08524SHans Verkuil * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> 18d1c4ecdeSMauro Carvalho Chehab * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> 19f8c08524SHans Verkuil * 20f8c08524SHans Verkuil * Note: this card seems to swap the left and right audio channels! 21f8c08524SHans Verkuil * 22f8c08524SHans Verkuil * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. 231da177e4SLinus Torvalds */ 241da177e4SLinus Torvalds 251da177e4SLinus Torvalds #include <linux/module.h> /* Modules */ 261da177e4SLinus Torvalds #include <linux/init.h> /* Initdata */ 27fb911ee8SPeter Osterlund #include <linux/ioport.h> /* request_region */ 281da177e4SLinus Torvalds #include <linux/delay.h> /* udelay */ 29d1c4ecdeSMauro Carvalho Chehab #include <linux/videodev2.h> /* kernel radio structs */ 30502b71b5SHans Verkuil #include <linux/mutex.h> 31502b71b5SHans Verkuil #include <linux/io.h> /* outb, outb_p */ 329f1dfccfSHans Verkuil #include <linux/slab.h> 33502b71b5SHans Verkuil #include <media/v4l2-ioctl.h> 34502b71b5SHans Verkuil #include <media/v4l2-device.h> 35f8c08524SHans Verkuil #include "radio-isa.h" 36502b71b5SHans Verkuil 374753647eSPekka Seppanen /* 384753647eSPekka Seppanen * Module info. 394753647eSPekka Seppanen */ 404753647eSPekka Seppanen 4129834c1aSMauro Carvalho Chehab MODULE_AUTHOR("Jonas Munsin, Pekka Seppänen <pexu@kapsi.fi>"); 424753647eSPekka Seppanen MODULE_DESCRIPTION("A driver for the GemTek Radio card."); 434753647eSPekka Seppanen MODULE_LICENSE("GPL"); 44f8c08524SHans Verkuil MODULE_VERSION("1.0.0"); 454753647eSPekka Seppanen 464753647eSPekka Seppanen /* 474753647eSPekka Seppanen * Module params. 484753647eSPekka Seppanen */ 494753647eSPekka Seppanen 504753647eSPekka Seppanen #ifndef CONFIG_RADIO_GEMTEK_PORT 514753647eSPekka Seppanen #define CONFIG_RADIO_GEMTEK_PORT -1 524753647eSPekka Seppanen #endif 534753647eSPekka Seppanen #ifndef CONFIG_RADIO_GEMTEK_PROBE 544753647eSPekka Seppanen #define CONFIG_RADIO_GEMTEK_PROBE 1 554753647eSPekka Seppanen #endif 564753647eSPekka Seppanen 57f8c08524SHans Verkuil #define GEMTEK_MAX 4 58f8c08524SHans Verkuil 5990ab5ee9SRusty Russell static bool probe = CONFIG_RADIO_GEMTEK_PROBE; 6090ab5ee9SRusty Russell static bool hardmute; 61f8c08524SHans Verkuil static int io[GEMTEK_MAX] = { [0] = CONFIG_RADIO_GEMTEK_PORT, 62f8c08524SHans Verkuil [1 ... (GEMTEK_MAX - 1)] = -1 }; 63f8c08524SHans Verkuil static int radio_nr[GEMTEK_MAX] = { [0 ... (GEMTEK_MAX - 1)] = -1 }; 644753647eSPekka Seppanen 65f8c08524SHans Verkuil module_param(probe, bool, 0444); 66f8c08524SHans Verkuil MODULE_PARM_DESC(probe, "Enable automatic device probing."); 67f8c08524SHans Verkuil 68f8c08524SHans Verkuil module_param(hardmute, bool, 0644); 69f8c08524SHans Verkuil MODULE_PARM_DESC(hardmute, "Enable 'hard muting' by shutting down PLL, may " 70f8c08524SHans Verkuil "reduce static noise."); 71f8c08524SHans Verkuil 72f8c08524SHans Verkuil module_param_array(io, int, NULL, 0444); 73f8c08524SHans Verkuil MODULE_PARM_DESC(io, "Force I/O ports for the GemTek Radio card if automatic " 744753647eSPekka Seppanen "probing is disabled or fails. The most common I/O ports are: 0x20c " 754753647eSPekka Seppanen "0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to " 764753647eSPekka Seppanen "work for the combined sound/radiocard)."); 774753647eSPekka Seppanen 78f8c08524SHans Verkuil module_param_array(radio_nr, int, NULL, 0444); 79f8c08524SHans Verkuil MODULE_PARM_DESC(radio_nr, "Radio device numbers"); 804753647eSPekka Seppanen 81857e594aSTrent Piepho /* 82857e594aSTrent Piepho * Frequency calculation constants. Intermediate frequency 10.52 MHz (nominal 83857e594aSTrent Piepho * value 10.7 MHz), reference divisor 6.39 kHz (nominal 6.25 kHz). 84857e594aSTrent Piepho */ 85857e594aSTrent Piepho #define FSCALE 8 86857e594aSTrent Piepho #define IF_OFFSET ((unsigned int)(10.52 * 16000 * (1<<FSCALE))) 87857e594aSTrent Piepho #define REF_FREQ ((unsigned int)(6.39 * 16 * (1<<FSCALE))) 88857e594aSTrent Piepho 894753647eSPekka Seppanen #define GEMTEK_CK 0x01 /* Clock signal */ 904753647eSPekka Seppanen #define GEMTEK_DA 0x02 /* Serial data */ 914753647eSPekka Seppanen #define GEMTEK_CE 0x04 /* Chip enable */ 924753647eSPekka Seppanen #define GEMTEK_NS 0x08 /* No signal */ 934753647eSPekka Seppanen #define GEMTEK_MT 0x10 /* Line mute */ 944753647eSPekka Seppanen #define GEMTEK_STDF_3_125_KHZ 0x01 /* Standard frequency 3.125 kHz */ 954753647eSPekka Seppanen #define GEMTEK_PLL_OFF 0x07 /* PLL off */ 964753647eSPekka Seppanen 974753647eSPekka Seppanen #define BU2614_BUS_SIZE 32 /* BU2614 / BU2614FS bus size */ 984753647eSPekka Seppanen 994753647eSPekka Seppanen #define SHORT_DELAY 5 /* usec */ 1004753647eSPekka Seppanen #define LONG_DELAY 75 /* usec */ 1014753647eSPekka Seppanen 102502b71b5SHans Verkuil struct gemtek { 103f8c08524SHans Verkuil struct radio_isa_card isa; 104f8c08524SHans Verkuil bool muted; 105b25be979STrent Piepho u32 bu2614data; 1064753647eSPekka Seppanen }; 1074753647eSPekka Seppanen 108b25be979STrent Piepho #define BU2614_FREQ_BITS 16 /* D0..D15, Frequency data */ 109b25be979STrent Piepho #define BU2614_PORT_BITS 3 /* P0..P2, Output port control data */ 110b25be979STrent Piepho #define BU2614_VOID_BITS 4 /* unused */ 111b25be979STrent Piepho #define BU2614_FMES_BITS 1 /* CT, Frequency measurement beginning data */ 112b25be979STrent Piepho #define BU2614_STDF_BITS 3 /* R0..R2, Standard frequency data */ 113b25be979STrent Piepho #define BU2614_SWIN_BITS 1 /* S, Switch between FMIN / AMIN */ 114b25be979STrent Piepho #define BU2614_SWAL_BITS 1 /* PS, Swallow counter division (AMIN only)*/ 115b25be979STrent Piepho #define BU2614_VOID2_BITS 1 /* unused */ 116b25be979STrent Piepho #define BU2614_FMUN_BITS 1 /* GT, Frequency measurement time & unlock */ 117b25be979STrent Piepho #define BU2614_TEST_BITS 1 /* TS, Test data is input */ 1184753647eSPekka Seppanen 119b25be979STrent Piepho #define BU2614_FREQ_SHIFT 0 120b25be979STrent Piepho #define BU2614_PORT_SHIFT (BU2614_FREQ_BITS + BU2614_FREQ_SHIFT) 121b25be979STrent Piepho #define BU2614_VOID_SHIFT (BU2614_PORT_BITS + BU2614_PORT_SHIFT) 122b25be979STrent Piepho #define BU2614_FMES_SHIFT (BU2614_VOID_BITS + BU2614_VOID_SHIFT) 123b25be979STrent Piepho #define BU2614_STDF_SHIFT (BU2614_FMES_BITS + BU2614_FMES_SHIFT) 124b25be979STrent Piepho #define BU2614_SWIN_SHIFT (BU2614_STDF_BITS + BU2614_STDF_SHIFT) 125b25be979STrent Piepho #define BU2614_SWAL_SHIFT (BU2614_SWIN_BITS + BU2614_SWIN_SHIFT) 126b25be979STrent Piepho #define BU2614_VOID2_SHIFT (BU2614_SWAL_BITS + BU2614_SWAL_SHIFT) 127b25be979STrent Piepho #define BU2614_FMUN_SHIFT (BU2614_VOID2_BITS + BU2614_VOID2_SHIFT) 128b25be979STrent Piepho #define BU2614_TEST_SHIFT (BU2614_FMUN_BITS + BU2614_FMUN_SHIFT) 129b25be979STrent Piepho 130b25be979STrent Piepho #define MKMASK(field) (((1<<BU2614_##field##_BITS) - 1) << \ 131b25be979STrent Piepho BU2614_##field##_SHIFT) 132b25be979STrent Piepho #define BU2614_PORT_MASK MKMASK(PORT) 133b25be979STrent Piepho #define BU2614_FREQ_MASK MKMASK(FREQ) 134b25be979STrent Piepho #define BU2614_VOID_MASK MKMASK(VOID) 135b25be979STrent Piepho #define BU2614_FMES_MASK MKMASK(FMES) 136b25be979STrent Piepho #define BU2614_STDF_MASK MKMASK(STDF) 137b25be979STrent Piepho #define BU2614_SWIN_MASK MKMASK(SWIN) 138b25be979STrent Piepho #define BU2614_SWAL_MASK MKMASK(SWAL) 139b25be979STrent Piepho #define BU2614_VOID2_MASK MKMASK(VOID2) 140b25be979STrent Piepho #define BU2614_FMUN_MASK MKMASK(FMUN) 141b25be979STrent Piepho #define BU2614_TEST_MASK MKMASK(TEST) 1424753647eSPekka Seppanen 1434753647eSPekka Seppanen /* 1444753647eSPekka Seppanen * Set data which will be sent to BU2614FS. 1454753647eSPekka Seppanen */ 146b25be979STrent Piepho #define gemtek_bu2614_set(dev, field, data) ((dev)->bu2614data = \ 147b25be979STrent Piepho ((dev)->bu2614data & ~field##_MASK) | ((data) << field##_SHIFT)) 1484753647eSPekka Seppanen 1494753647eSPekka Seppanen /* 1504753647eSPekka Seppanen * Transmit settings to BU2614FS over GemTek IC. 1514753647eSPekka Seppanen */ 152502b71b5SHans Verkuil static void gemtek_bu2614_transmit(struct gemtek *gt) 1534753647eSPekka Seppanen { 154f8c08524SHans Verkuil struct radio_isa_card *isa = >->isa; 1554753647eSPekka Seppanen int i, bit, q, mute; 1564753647eSPekka Seppanen 157502b71b5SHans Verkuil mute = gt->muted ? GEMTEK_MT : 0x00; 1584753647eSPekka Seppanen 159f8c08524SHans Verkuil outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, isa->io); 1604753647eSPekka Seppanen udelay(LONG_DELAY); 1614753647eSPekka Seppanen 162502b71b5SHans Verkuil for (i = 0, q = gt->bu2614data; i < 32; i++, q >>= 1) { 163b25be979STrent Piepho bit = (q & 1) ? GEMTEK_DA : 0; 164f8c08524SHans Verkuil outb_p(mute | GEMTEK_CE | bit, isa->io); 1654753647eSPekka Seppanen udelay(SHORT_DELAY); 166f8c08524SHans Verkuil outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, isa->io); 1674753647eSPekka Seppanen udelay(SHORT_DELAY); 1684753647eSPekka Seppanen } 1694753647eSPekka Seppanen 170f8c08524SHans Verkuil outb_p(mute | GEMTEK_DA | GEMTEK_CK, isa->io); 1714753647eSPekka Seppanen udelay(SHORT_DELAY); 1724753647eSPekka Seppanen } 1734753647eSPekka Seppanen 1744753647eSPekka Seppanen /* 175857e594aSTrent Piepho * Calculate divisor from FM-frequency for BU2614FS (3.125 KHz STDF expected). 1764753647eSPekka Seppanen */ 177857e594aSTrent Piepho static unsigned long gemtek_convfreq(unsigned long freq) 1784753647eSPekka Seppanen { 179857e594aSTrent Piepho return ((freq << FSCALE) + IF_OFFSET + REF_FREQ / 2) / REF_FREQ; 1804753647eSPekka Seppanen } 1814753647eSPekka Seppanen 182f8c08524SHans Verkuil static struct radio_isa_card *gemtek_alloc(void) 183f8c08524SHans Verkuil { 184f8c08524SHans Verkuil struct gemtek *gt = kzalloc(sizeof(*gt), GFP_KERNEL); 185f8c08524SHans Verkuil 186f8c08524SHans Verkuil if (gt) 187f8c08524SHans Verkuil gt->muted = true; 188f8c08524SHans Verkuil return gt ? >->isa : NULL; 189f8c08524SHans Verkuil } 190f8c08524SHans Verkuil 1914753647eSPekka Seppanen /* 1924753647eSPekka Seppanen * Set FM-frequency. 1934753647eSPekka Seppanen */ 194f8c08524SHans Verkuil static int gemtek_s_frequency(struct radio_isa_card *isa, u32 freq) 1954753647eSPekka Seppanen { 196f8c08524SHans Verkuil struct gemtek *gt = container_of(isa, struct gemtek, isa); 1974753647eSPekka Seppanen 198f8c08524SHans Verkuil if (hardmute && gt->muted) 199f8c08524SHans Verkuil return 0; 2004753647eSPekka Seppanen 201502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_PORT, 0); 202502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_FMES, 0); 203502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_SWIN, 0); /* FM-mode */ 204502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_SWAL, 0); 205502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_FMUN, 1); /* GT bit set */ 206502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_TEST, 0); 207502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_STDF_3_125_KHZ); 208502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_FREQ, gemtek_convfreq(freq)); 209502b71b5SHans Verkuil gemtek_bu2614_transmit(gt); 210f8c08524SHans Verkuil return 0; 2114753647eSPekka Seppanen } 2124753647eSPekka Seppanen 2134753647eSPekka Seppanen /* 2144753647eSPekka Seppanen * Set mute flag. 2154753647eSPekka Seppanen */ 216f8c08524SHans Verkuil static int gemtek_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 2174753647eSPekka Seppanen { 218f8c08524SHans Verkuil struct gemtek *gt = container_of(isa, struct gemtek, isa); 2194753647eSPekka Seppanen int i; 220502b71b5SHans Verkuil 221f8c08524SHans Verkuil gt->muted = mute; 2224753647eSPekka Seppanen if (hardmute) { 223f8c08524SHans Verkuil if (!mute) 224f8c08524SHans Verkuil return gemtek_s_frequency(isa, isa->freq); 225f8c08524SHans Verkuil 2264753647eSPekka Seppanen /* Turn off PLL, disable data output */ 227502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_PORT, 0); 228502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_FMES, 0); /* CT bit off */ 229502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_SWIN, 0); /* FM-mode */ 230502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_SWAL, 0); 231502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_FMUN, 0); /* GT bit off */ 232502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_TEST, 0); 233502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_PLL_OFF); 234502b71b5SHans Verkuil gemtek_bu2614_set(gt, BU2614_FREQ, 0); 235502b71b5SHans Verkuil gemtek_bu2614_transmit(gt); 236f8c08524SHans Verkuil return 0; 237502b71b5SHans Verkuil } 238502b71b5SHans Verkuil 2394753647eSPekka Seppanen /* Read bus contents (CE, CK and DA). */ 240f8c08524SHans Verkuil i = inb_p(isa->io); 2414753647eSPekka Seppanen /* Write it back with mute flag set. */ 242f8c08524SHans Verkuil outb_p((i >> 5) | (mute ? GEMTEK_MT : 0), isa->io); 2434753647eSPekka Seppanen udelay(SHORT_DELAY); 244f8c08524SHans Verkuil return 0; 2454753647eSPekka Seppanen } 2464753647eSPekka Seppanen 247f8c08524SHans Verkuil static u32 gemtek_g_rxsubchans(struct radio_isa_card *isa) 2484753647eSPekka Seppanen { 249f8c08524SHans Verkuil if (inb_p(isa->io) & GEMTEK_NS) 250f8c08524SHans Verkuil return V4L2_TUNER_SUB_MONO; 251f8c08524SHans Verkuil return V4L2_TUNER_SUB_STEREO; 2524753647eSPekka Seppanen } 2534753647eSPekka Seppanen 2544753647eSPekka Seppanen /* 2554753647eSPekka Seppanen * Check if requested card acts like GemTek Radio card. 2564753647eSPekka Seppanen */ 257f8c08524SHans Verkuil static bool gemtek_probe(struct radio_isa_card *isa, int io) 2584753647eSPekka Seppanen { 2594753647eSPekka Seppanen int i, q; 2604753647eSPekka Seppanen 261f8c08524SHans Verkuil q = inb_p(io); /* Read bus contents before probing. */ 2624753647eSPekka Seppanen /* Try to turn on CE, CK and DA respectively and check if card responds 2634753647eSPekka Seppanen properly. */ 2644753647eSPekka Seppanen for (i = 0; i < 3; ++i) { 265f8c08524SHans Verkuil outb_p(1 << i, io); 2664753647eSPekka Seppanen udelay(SHORT_DELAY); 2674753647eSPekka Seppanen 268f8c08524SHans Verkuil if ((inb_p(io) & ~GEMTEK_NS) != (0x17 | (1 << (i + 5)))) 269f8c08524SHans Verkuil return false; 2704753647eSPekka Seppanen } 271f8c08524SHans Verkuil outb_p(q >> 5, io); /* Write bus contents back. */ 2724753647eSPekka Seppanen udelay(SHORT_DELAY); 273f8c08524SHans Verkuil return true; 2744753647eSPekka Seppanen } 2754753647eSPekka Seppanen 276f8c08524SHans Verkuil static const struct radio_isa_ops gemtek_ops = { 277f8c08524SHans Verkuil .alloc = gemtek_alloc, 278f8c08524SHans Verkuil .probe = gemtek_probe, 279f8c08524SHans Verkuil .s_mute_volume = gemtek_s_mute_volume, 280f8c08524SHans Verkuil .s_frequency = gemtek_s_frequency, 281f8c08524SHans Verkuil .g_rxsubchans = gemtek_g_rxsubchans, 2821da177e4SLinus Torvalds }; 2831da177e4SLinus Torvalds 284f8c08524SHans Verkuil static const int gemtek_ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c }; 285e9bb9c64SDouglas Landgraf 286f8c08524SHans Verkuil static struct radio_isa_driver gemtek_driver = { 287f8c08524SHans Verkuil .driver = { 288f8c08524SHans Verkuil .match = radio_isa_match, 289f8c08524SHans Verkuil .probe = radio_isa_probe, 290f8c08524SHans Verkuil .remove = radio_isa_remove, 291f8c08524SHans Verkuil .driver = { 292f8c08524SHans Verkuil .name = "radio-gemtek", 293f8c08524SHans Verkuil }, 294f8c08524SHans Verkuil }, 295f8c08524SHans Verkuil .io_params = io, 296f8c08524SHans Verkuil .radio_nr_params = radio_nr, 297f8c08524SHans Verkuil .io_ports = gemtek_ioports, 298f8c08524SHans Verkuil .num_of_io_ports = ARRAY_SIZE(gemtek_ioports), 299f8c08524SHans Verkuil .region_size = 1, 300f8c08524SHans Verkuil .card = "GemTek Radio", 301f8c08524SHans Verkuil .ops = &gemtek_ops, 302f8c08524SHans Verkuil .has_stereo = true, 3031da177e4SLinus Torvalds }; 3041da177e4SLinus Torvalds 3051da177e4SLinus Torvalds static int __init gemtek_init(void) 3061da177e4SLinus Torvalds { 307f8c08524SHans Verkuil gemtek_driver.probe = probe; 308f8c08524SHans Verkuil return isa_register_driver(&gemtek_driver.driver, GEMTEK_MAX); 3091da177e4SLinus Torvalds } 3101da177e4SLinus Torvalds 3114753647eSPekka Seppanen static void __exit gemtek_exit(void) 3121da177e4SLinus Torvalds { 3134753647eSPekka Seppanen hardmute = 1; /* Turn off PLL */ 314f8c08524SHans Verkuil isa_unregister_driver(&gemtek_driver.driver); 3151da177e4SLinus Torvalds } 3161da177e4SLinus Torvalds 3171da177e4SLinus Torvalds module_init(gemtek_init); 3184753647eSPekka Seppanen module_exit(gemtek_exit); 319