162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Typhoon Radio Card driver for radio support 362306a36Sopenharmony_ci * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de> 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Notes on the hardware 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This card has two output sockets, one for speakers and one for line. 862306a36Sopenharmony_ci * The speaker output has volume control, but only in four discrete 962306a36Sopenharmony_ci * steps. The line output has neither volume control nor mute. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * The card has auto-stereo according to its manual, although it all 1262306a36Sopenharmony_ci * sounds mono to me (even with the Win/DOS drivers). Maybe it's my 1362306a36Sopenharmony_ci * antenna - I really don't know for sure. 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Frequency control is done digitally. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Volume control is done digitally, but there are only four different 1862306a36Sopenharmony_ci * possible values. So you should better always turn the volume up and 1962306a36Sopenharmony_ci * use line control. I got the best results by connecting line output 2062306a36Sopenharmony_ci * to the sound card microphone input. For such a configuration the 2162306a36Sopenharmony_ci * volume control has no effect, since volume control only influences 2262306a36Sopenharmony_ci * the speaker output. 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * There is no explicit mute/unmute. So I set the radio frequency to a 2562306a36Sopenharmony_ci * value where I do expect just noise and turn the speaker volume down. 2662306a36Sopenharmony_ci * The frequency change is necessary since the card never seems to be 2762306a36Sopenharmony_ci * completely silent. 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org> 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#include <linux/module.h> /* Modules */ 3362306a36Sopenharmony_ci#include <linux/init.h> /* Initdata */ 3462306a36Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 3562306a36Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 3662306a36Sopenharmony_ci#include <linux/io.h> /* outb, outb_p */ 3762306a36Sopenharmony_ci#include <linux/slab.h> 3862306a36Sopenharmony_ci#include <media/v4l2-device.h> 3962306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 4062306a36Sopenharmony_ci#include "radio-isa.h" 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define DRIVER_VERSION "0.1.2" 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ciMODULE_AUTHOR("Dr. Henrik Seidel"); 4562306a36Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); 4662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 4762306a36Sopenharmony_ciMODULE_VERSION("0.1.99"); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#ifndef CONFIG_RADIO_TYPHOON_PORT 5062306a36Sopenharmony_ci#define CONFIG_RADIO_TYPHOON_PORT -1 5162306a36Sopenharmony_ci#endif 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ 5462306a36Sopenharmony_ci#define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000 5562306a36Sopenharmony_ci#endif 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci#define TYPHOON_MAX 2 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT, 6062306a36Sopenharmony_ci [1 ... (TYPHOON_MAX - 1)] = -1 }; 6162306a36Sopenharmony_cistatic int radio_nr[TYPHOON_MAX] = { [0 ... (TYPHOON_MAX - 1)] = -1 }; 6262306a36Sopenharmony_cistatic unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cimodule_param_array(io, int, NULL, 0444); 6562306a36Sopenharmony_ciMODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)"); 6662306a36Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444); 6762306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device numbers"); 6862306a36Sopenharmony_cimodule_param(mutefreq, ulong, 0); 6962306a36Sopenharmony_ciMODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistruct typhoon { 7262306a36Sopenharmony_ci struct radio_isa_card isa; 7362306a36Sopenharmony_ci int muted; 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic struct radio_isa_card *typhoon_alloc(void) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci return ty ? &ty->isa : NULL; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci unsigned long outval; 8662306a36Sopenharmony_ci unsigned long x; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* 8962306a36Sopenharmony_ci * The frequency transfer curve is not linear. The best fit I could 9062306a36Sopenharmony_ci * get is 9162306a36Sopenharmony_ci * 9262306a36Sopenharmony_ci * outval = -155 + exp((f + 15.55) * 0.057)) 9362306a36Sopenharmony_ci * 9462306a36Sopenharmony_ci * where frequency f is in MHz. Since we don't have exp in the kernel, 9562306a36Sopenharmony_ci * I approximate this function by a third order polynomial. 9662306a36Sopenharmony_ci * 9762306a36Sopenharmony_ci */ 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci x = freq / 160; 10062306a36Sopenharmony_ci outval = (x * x + 2500) / 5000; 10162306a36Sopenharmony_ci outval = (outval * x + 5000) / 10000; 10262306a36Sopenharmony_ci outval -= (10 * x * x + 10433) / 20866; 10362306a36Sopenharmony_ci outval += 4 * x - 11505; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci outb_p((outval >> 8) & 0x01, isa->io + 4); 10662306a36Sopenharmony_ci outb_p(outval >> 9, isa->io + 6); 10762306a36Sopenharmony_ci outb_p(outval & 0xff, isa->io + 8); 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci struct typhoon *ty = container_of(isa, struct typhoon, isa); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (mute) 11662306a36Sopenharmony_ci vol = 0; 11762306a36Sopenharmony_ci vol >>= 14; /* Map 16 bit to 2 bit */ 11862306a36Sopenharmony_ci vol &= 3; 11962306a36Sopenharmony_ci outb_p(vol / 2, isa->io); /* Set the volume, high bit. */ 12062306a36Sopenharmony_ci outb_p(vol % 2, isa->io + 2); /* Set the volume, low bit. */ 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (vol == 0 && !ty->muted) { 12362306a36Sopenharmony_ci ty->muted = true; 12462306a36Sopenharmony_ci return typhoon_s_frequency(isa, mutefreq << 4); 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci if (vol && ty->muted) { 12762306a36Sopenharmony_ci ty->muted = false; 12862306a36Sopenharmony_ci return typhoon_s_frequency(isa, isa->freq); 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic const struct radio_isa_ops typhoon_ops = { 13462306a36Sopenharmony_ci .alloc = typhoon_alloc, 13562306a36Sopenharmony_ci .s_mute_volume = typhoon_s_mute_volume, 13662306a36Sopenharmony_ci .s_frequency = typhoon_s_frequency, 13762306a36Sopenharmony_ci}; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic const int typhoon_ioports[] = { 0x316, 0x336 }; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic struct radio_isa_driver typhoon_driver = { 14262306a36Sopenharmony_ci .driver = { 14362306a36Sopenharmony_ci .match = radio_isa_match, 14462306a36Sopenharmony_ci .probe = radio_isa_probe, 14562306a36Sopenharmony_ci .remove = radio_isa_remove, 14662306a36Sopenharmony_ci .driver = { 14762306a36Sopenharmony_ci .name = "radio-typhoon", 14862306a36Sopenharmony_ci }, 14962306a36Sopenharmony_ci }, 15062306a36Sopenharmony_ci .io_params = io, 15162306a36Sopenharmony_ci .radio_nr_params = radio_nr, 15262306a36Sopenharmony_ci .io_ports = typhoon_ioports, 15362306a36Sopenharmony_ci .num_of_io_ports = ARRAY_SIZE(typhoon_ioports), 15462306a36Sopenharmony_ci .region_size = 8, 15562306a36Sopenharmony_ci .card = "Typhoon Radio", 15662306a36Sopenharmony_ci .ops = &typhoon_ops, 15762306a36Sopenharmony_ci .has_stereo = true, 15862306a36Sopenharmony_ci .max_volume = 3, 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int __init typhoon_init(void) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci if (mutefreq < 87000 || mutefreq > 108000) { 16462306a36Sopenharmony_ci printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n", 16562306a36Sopenharmony_ci typhoon_driver.driver.driver.name); 16662306a36Sopenharmony_ci printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n", 16762306a36Sopenharmony_ci typhoon_driver.driver.driver.name); 16862306a36Sopenharmony_ci return -ENODEV; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX); 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic void __exit typhoon_exit(void) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci isa_unregister_driver(&typhoon_driver.driver); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cimodule_init(typhoon_init); 18062306a36Sopenharmony_cimodule_exit(typhoon_exit); 18162306a36Sopenharmony_ci 182