18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support 38c2ecf20Sopenharmony_ci * (c) 1999 R. Offermanns (rolf@offermanns.de) 48c2ecf20Sopenharmony_ci * based on the aimslab radio driver from M. Kirkwood 58c2ecf20Sopenharmony_ci * many thanks to Michael Becker and Friedhelm Birth (from TerraTec) 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * History: 98c2ecf20Sopenharmony_ci * 1999-05-21 First preview release 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Notes on the hardware: 128c2ecf20Sopenharmony_ci * There are two "main" chips on the card: 138c2ecf20Sopenharmony_ci * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf) 148c2ecf20Sopenharmony_ci * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf) 158c2ecf20Sopenharmony_ci * (you can get the datasheet at the above links) 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 188c2ecf20Sopenharmony_ci * Volume Control is done digitally 198c2ecf20Sopenharmony_ci * 208c2ecf20Sopenharmony_ci * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> 218c2ecf20Sopenharmony_ci * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org> 228c2ecf20Sopenharmony_ci */ 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <linux/module.h> /* Modules */ 258c2ecf20Sopenharmony_ci#include <linux/init.h> /* Initdata */ 268c2ecf20Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 278c2ecf20Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 288c2ecf20Sopenharmony_ci#include <linux/mutex.h> 298c2ecf20Sopenharmony_ci#include <linux/io.h> /* outb, outb_p */ 308c2ecf20Sopenharmony_ci#include <linux/slab.h> 318c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 328c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 338c2ecf20Sopenharmony_ci#include "radio-isa.h" 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ciMODULE_AUTHOR("R. Offermans & others"); 368c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); 378c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 388c2ecf20Sopenharmony_ciMODULE_VERSION("0.1.99"); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* Note: there seems to be only one possible port (0x590), but without 418c2ecf20Sopenharmony_ci hardware this is hard to verify. For now, this is the only one we will 428c2ecf20Sopenharmony_ci support. */ 438c2ecf20Sopenharmony_cistatic int io = 0x590; 448c2ecf20Sopenharmony_cistatic int radio_nr = -1; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cimodule_param(radio_nr, int, 0444); 478c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device number"); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define WRT_DIS 0x00 508c2ecf20Sopenharmony_ci#define CLK_OFF 0x00 518c2ecf20Sopenharmony_ci#define IIC_DATA 0x01 528c2ecf20Sopenharmony_ci#define IIC_CLK 0x02 538c2ecf20Sopenharmony_ci#define DATA 0x04 548c2ecf20Sopenharmony_ci#define CLK_ON 0x08 558c2ecf20Sopenharmony_ci#define WRT_EN 0x10 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic struct radio_isa_card *terratec_alloc(void) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL); 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci int i; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (mute) 678c2ecf20Sopenharmony_ci vol = 0; 688c2ecf20Sopenharmony_ci vol = vol + (vol * 32); /* change both channels */ 698c2ecf20Sopenharmony_ci for (i = 0; i < 8; i++) { 708c2ecf20Sopenharmony_ci if (vol & (0x80 >> i)) 718c2ecf20Sopenharmony_ci outb(0x80, isa->io + 1); 728c2ecf20Sopenharmony_ci else 738c2ecf20Sopenharmony_ci outb(0x00, isa->io + 1); 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci return 0; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci/* this is the worst part in this driver */ 808c2ecf20Sopenharmony_ci/* many more or less strange things are going on here, but hey, it works :) */ 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci int i; 858c2ecf20Sopenharmony_ci int p; 868c2ecf20Sopenharmony_ci int temp; 878c2ecf20Sopenharmony_ci long rest; 888c2ecf20Sopenharmony_ci unsigned char buffer[25]; /* we have to bit shift 25 registers */ 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci freq = freq / 160; /* convert the freq. to a nice to handle value */ 918c2ecf20Sopenharmony_ci memset(buffer, 0, sizeof(buffer)); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci rest = freq * 10 + 10700; /* I once had understood what is going on here */ 948c2ecf20Sopenharmony_ci /* maybe some wise guy (friedhelm?) can comment this stuff */ 958c2ecf20Sopenharmony_ci i = 13; 968c2ecf20Sopenharmony_ci p = 10; 978c2ecf20Sopenharmony_ci temp = 102400; 988c2ecf20Sopenharmony_ci while (rest != 0) { 998c2ecf20Sopenharmony_ci if (rest % temp == rest) 1008c2ecf20Sopenharmony_ci buffer[i] = 0; 1018c2ecf20Sopenharmony_ci else { 1028c2ecf20Sopenharmony_ci buffer[i] = 1; 1038c2ecf20Sopenharmony_ci rest = rest - temp; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci i--; 1068c2ecf20Sopenharmony_ci p--; 1078c2ecf20Sopenharmony_ci temp = temp / 2; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */ 1118c2ecf20Sopenharmony_ci if (buffer[i] == 1) { 1128c2ecf20Sopenharmony_ci outb(WRT_EN | DATA, isa->io); 1138c2ecf20Sopenharmony_ci outb(WRT_EN | DATA | CLK_ON, isa->io); 1148c2ecf20Sopenharmony_ci outb(WRT_EN | DATA, isa->io); 1158c2ecf20Sopenharmony_ci } else { 1168c2ecf20Sopenharmony_ci outb(WRT_EN | 0x00, isa->io); 1178c2ecf20Sopenharmony_ci outb(WRT_EN | 0x00 | CLK_ON, isa->io); 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci outb(0x00, isa->io); 1218c2ecf20Sopenharmony_ci return 0; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic u32 terratec_g_signal(struct radio_isa_card *isa) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci /* bit set = no signal present */ 1278c2ecf20Sopenharmony_ci return (inb(isa->io) & 2) ? 0 : 0xffff; 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic const struct radio_isa_ops terratec_ops = { 1318c2ecf20Sopenharmony_ci .alloc = terratec_alloc, 1328c2ecf20Sopenharmony_ci .s_mute_volume = terratec_s_mute_volume, 1338c2ecf20Sopenharmony_ci .s_frequency = terratec_s_frequency, 1348c2ecf20Sopenharmony_ci .g_signal = terratec_g_signal, 1358c2ecf20Sopenharmony_ci}; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic const int terratec_ioports[] = { 0x590 }; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic struct radio_isa_driver terratec_driver = { 1408c2ecf20Sopenharmony_ci .driver = { 1418c2ecf20Sopenharmony_ci .match = radio_isa_match, 1428c2ecf20Sopenharmony_ci .probe = radio_isa_probe, 1438c2ecf20Sopenharmony_ci .remove = radio_isa_remove, 1448c2ecf20Sopenharmony_ci .driver = { 1458c2ecf20Sopenharmony_ci .name = "radio-terratec", 1468c2ecf20Sopenharmony_ci }, 1478c2ecf20Sopenharmony_ci }, 1488c2ecf20Sopenharmony_ci .io_params = &io, 1498c2ecf20Sopenharmony_ci .radio_nr_params = &radio_nr, 1508c2ecf20Sopenharmony_ci .io_ports = terratec_ioports, 1518c2ecf20Sopenharmony_ci .num_of_io_ports = ARRAY_SIZE(terratec_ioports), 1528c2ecf20Sopenharmony_ci .region_size = 2, 1538c2ecf20Sopenharmony_ci .card = "TerraTec ActiveRadio", 1548c2ecf20Sopenharmony_ci .ops = &terratec_ops, 1558c2ecf20Sopenharmony_ci .has_stereo = true, 1568c2ecf20Sopenharmony_ci .max_volume = 10, 1578c2ecf20Sopenharmony_ci}; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int __init terratec_init(void) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci return isa_register_driver(&terratec_driver.driver, 1); 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic void __exit terratec_exit(void) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci isa_unregister_driver(&terratec_driver.driver); 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cimodule_init(terratec_init); 1708c2ecf20Sopenharmony_cimodule_exit(terratec_exit); 1718c2ecf20Sopenharmony_ci 172