18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* Typhoon Radio Card driver for radio support 38c2ecf20Sopenharmony_ci * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de> 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Notes on the hardware 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This card has two output sockets, one for speakers and one for line. 88c2ecf20Sopenharmony_ci * The speaker output has volume control, but only in four discrete 98c2ecf20Sopenharmony_ci * steps. The line output has neither volume control nor mute. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * The card has auto-stereo according to its manual, although it all 128c2ecf20Sopenharmony_ci * sounds mono to me (even with the Win/DOS drivers). Maybe it's my 138c2ecf20Sopenharmony_ci * antenna - I really don't know for sure. 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Frequency control is done digitally. 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * Volume control is done digitally, but there are only four different 188c2ecf20Sopenharmony_ci * possible values. So you should better always turn the volume up and 198c2ecf20Sopenharmony_ci * use line control. I got the best results by connecting line output 208c2ecf20Sopenharmony_ci * to the sound card microphone input. For such a configuration the 218c2ecf20Sopenharmony_ci * volume control has no effect, since volume control only influences 228c2ecf20Sopenharmony_ci * the speaker output. 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * There is no explicit mute/unmute. So I set the radio frequency to a 258c2ecf20Sopenharmony_ci * value where I do expect just noise and turn the speaker volume down. 268c2ecf20Sopenharmony_ci * The frequency change is necessary since the card never seems to be 278c2ecf20Sopenharmony_ci * completely silent. 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org> 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include <linux/module.h> /* Modules */ 338c2ecf20Sopenharmony_ci#include <linux/init.h> /* Initdata */ 348c2ecf20Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 358c2ecf20Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 368c2ecf20Sopenharmony_ci#include <linux/io.h> /* outb, outb_p */ 378c2ecf20Sopenharmony_ci#include <linux/slab.h> 388c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 398c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 408c2ecf20Sopenharmony_ci#include "radio-isa.h" 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define DRIVER_VERSION "0.1.2" 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dr. Henrik Seidel"); 458c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); 468c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 478c2ecf20Sopenharmony_ciMODULE_VERSION("0.1.99"); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#ifndef CONFIG_RADIO_TYPHOON_PORT 508c2ecf20Sopenharmony_ci#define CONFIG_RADIO_TYPHOON_PORT -1 518c2ecf20Sopenharmony_ci#endif 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ 548c2ecf20Sopenharmony_ci#define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000 558c2ecf20Sopenharmony_ci#endif 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci#define TYPHOON_MAX 2 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT, 608c2ecf20Sopenharmony_ci [1 ... (TYPHOON_MAX - 1)] = -1 }; 618c2ecf20Sopenharmony_cistatic int radio_nr[TYPHOON_MAX] = { [0 ... (TYPHOON_MAX - 1)] = -1 }; 628c2ecf20Sopenharmony_cistatic unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cimodule_param_array(io, int, NULL, 0444); 658c2ecf20Sopenharmony_ciMODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)"); 668c2ecf20Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444); 678c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device numbers"); 688c2ecf20Sopenharmony_cimodule_param(mutefreq, ulong, 0); 698c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistruct typhoon { 728c2ecf20Sopenharmony_ci struct radio_isa_card isa; 738c2ecf20Sopenharmony_ci int muted; 748c2ecf20Sopenharmony_ci}; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic struct radio_isa_card *typhoon_alloc(void) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return ty ? &ty->isa : NULL; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci unsigned long outval; 868c2ecf20Sopenharmony_ci unsigned long x; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci /* 898c2ecf20Sopenharmony_ci * The frequency transfer curve is not linear. The best fit I could 908c2ecf20Sopenharmony_ci * get is 918c2ecf20Sopenharmony_ci * 928c2ecf20Sopenharmony_ci * outval = -155 + exp((f + 15.55) * 0.057)) 938c2ecf20Sopenharmony_ci * 948c2ecf20Sopenharmony_ci * where frequency f is in MHz. Since we don't have exp in the kernel, 958c2ecf20Sopenharmony_ci * I approximate this function by a third order polynomial. 968c2ecf20Sopenharmony_ci * 978c2ecf20Sopenharmony_ci */ 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci x = freq / 160; 1008c2ecf20Sopenharmony_ci outval = (x * x + 2500) / 5000; 1018c2ecf20Sopenharmony_ci outval = (outval * x + 5000) / 10000; 1028c2ecf20Sopenharmony_ci outval -= (10 * x * x + 10433) / 20866; 1038c2ecf20Sopenharmony_ci outval += 4 * x - 11505; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci outb_p((outval >> 8) & 0x01, isa->io + 4); 1068c2ecf20Sopenharmony_ci outb_p(outval >> 9, isa->io + 6); 1078c2ecf20Sopenharmony_ci outb_p(outval & 0xff, isa->io + 8); 1088c2ecf20Sopenharmony_ci return 0; 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci struct typhoon *ty = container_of(isa, struct typhoon, isa); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (mute) 1168c2ecf20Sopenharmony_ci vol = 0; 1178c2ecf20Sopenharmony_ci vol >>= 14; /* Map 16 bit to 2 bit */ 1188c2ecf20Sopenharmony_ci vol &= 3; 1198c2ecf20Sopenharmony_ci outb_p(vol / 2, isa->io); /* Set the volume, high bit. */ 1208c2ecf20Sopenharmony_ci outb_p(vol % 2, isa->io + 2); /* Set the volume, low bit. */ 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci if (vol == 0 && !ty->muted) { 1238c2ecf20Sopenharmony_ci ty->muted = true; 1248c2ecf20Sopenharmony_ci return typhoon_s_frequency(isa, mutefreq << 4); 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci if (vol && ty->muted) { 1278c2ecf20Sopenharmony_ci ty->muted = false; 1288c2ecf20Sopenharmony_ci return typhoon_s_frequency(isa, isa->freq); 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci return 0; 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic const struct radio_isa_ops typhoon_ops = { 1348c2ecf20Sopenharmony_ci .alloc = typhoon_alloc, 1358c2ecf20Sopenharmony_ci .s_mute_volume = typhoon_s_mute_volume, 1368c2ecf20Sopenharmony_ci .s_frequency = typhoon_s_frequency, 1378c2ecf20Sopenharmony_ci}; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic const int typhoon_ioports[] = { 0x316, 0x336 }; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic struct radio_isa_driver typhoon_driver = { 1428c2ecf20Sopenharmony_ci .driver = { 1438c2ecf20Sopenharmony_ci .match = radio_isa_match, 1448c2ecf20Sopenharmony_ci .probe = radio_isa_probe, 1458c2ecf20Sopenharmony_ci .remove = radio_isa_remove, 1468c2ecf20Sopenharmony_ci .driver = { 1478c2ecf20Sopenharmony_ci .name = "radio-typhoon", 1488c2ecf20Sopenharmony_ci }, 1498c2ecf20Sopenharmony_ci }, 1508c2ecf20Sopenharmony_ci .io_params = io, 1518c2ecf20Sopenharmony_ci .radio_nr_params = radio_nr, 1528c2ecf20Sopenharmony_ci .io_ports = typhoon_ioports, 1538c2ecf20Sopenharmony_ci .num_of_io_ports = ARRAY_SIZE(typhoon_ioports), 1548c2ecf20Sopenharmony_ci .region_size = 8, 1558c2ecf20Sopenharmony_ci .card = "Typhoon Radio", 1568c2ecf20Sopenharmony_ci .ops = &typhoon_ops, 1578c2ecf20Sopenharmony_ci .has_stereo = true, 1588c2ecf20Sopenharmony_ci .max_volume = 3, 1598c2ecf20Sopenharmony_ci}; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic int __init typhoon_init(void) 1628c2ecf20Sopenharmony_ci{ 1638c2ecf20Sopenharmony_ci if (mutefreq < 87000 || mutefreq > 108000) { 1648c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n", 1658c2ecf20Sopenharmony_ci typhoon_driver.driver.driver.name); 1668c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n", 1678c2ecf20Sopenharmony_ci typhoon_driver.driver.driver.name); 1688c2ecf20Sopenharmony_ci return -ENODEV; 1698c2ecf20Sopenharmony_ci } 1708c2ecf20Sopenharmony_ci return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX); 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cistatic void __exit typhoon_exit(void) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci isa_unregister_driver(&typhoon_driver.driver); 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cimodule_init(typhoon_init); 1808c2ecf20Sopenharmony_cimodule_exit(typhoon_exit); 1818c2ecf20Sopenharmony_ci 182