162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support 362306a36Sopenharmony_ci * (c) 1999 R. Offermanns (rolf@offermanns.de) 462306a36Sopenharmony_ci * based on the aimslab radio driver from M. Kirkwood 562306a36Sopenharmony_ci * many thanks to Michael Becker and Friedhelm Birth (from TerraTec) 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * History: 962306a36Sopenharmony_ci * 1999-05-21 First preview release 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Notes on the hardware: 1262306a36Sopenharmony_ci * There are two "main" chips on the card: 1362306a36Sopenharmony_ci * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf) 1462306a36Sopenharmony_ci * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf) 1562306a36Sopenharmony_ci * (you can get the datasheet at the above links) 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); 1862306a36Sopenharmony_ci * Volume Control is done digitally 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> 2162306a36Sopenharmony_ci * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org> 2262306a36Sopenharmony_ci */ 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include <linux/module.h> /* Modules */ 2562306a36Sopenharmony_ci#include <linux/init.h> /* Initdata */ 2662306a36Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 2762306a36Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 2862306a36Sopenharmony_ci#include <linux/mutex.h> 2962306a36Sopenharmony_ci#include <linux/io.h> /* outb, outb_p */ 3062306a36Sopenharmony_ci#include <linux/slab.h> 3162306a36Sopenharmony_ci#include <media/v4l2-device.h> 3262306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 3362306a36Sopenharmony_ci#include "radio-isa.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ciMODULE_AUTHOR("R. Offermans & others"); 3662306a36Sopenharmony_ciMODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); 3762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 3862306a36Sopenharmony_ciMODULE_VERSION("0.1.99"); 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/* Note: there seems to be only one possible port (0x590), but without 4162306a36Sopenharmony_ci hardware this is hard to verify. For now, this is the only one we will 4262306a36Sopenharmony_ci support. */ 4362306a36Sopenharmony_cistatic int io = 0x590; 4462306a36Sopenharmony_cistatic int radio_nr = -1; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cimodule_param(radio_nr, int, 0444); 4762306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device number"); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define WRT_DIS 0x00 5062306a36Sopenharmony_ci#define CLK_OFF 0x00 5162306a36Sopenharmony_ci#define IIC_DATA 0x01 5262306a36Sopenharmony_ci#define IIC_CLK 0x02 5362306a36Sopenharmony_ci#define DATA 0x04 5462306a36Sopenharmony_ci#define CLK_ON 0x08 5562306a36Sopenharmony_ci#define WRT_EN 0x10 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic struct radio_isa_card *terratec_alloc(void) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci int i; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (mute) 6762306a36Sopenharmony_ci vol = 0; 6862306a36Sopenharmony_ci vol = vol + (vol * 32); /* change both channels */ 6962306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 7062306a36Sopenharmony_ci if (vol & (0x80 >> i)) 7162306a36Sopenharmony_ci outb(0x80, isa->io + 1); 7262306a36Sopenharmony_ci else 7362306a36Sopenharmony_ci outb(0x00, isa->io + 1); 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/* this is the worst part in this driver */ 8062306a36Sopenharmony_ci/* many more or less strange things are going on here, but hey, it works :) */ 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci int i; 8562306a36Sopenharmony_ci int temp; 8662306a36Sopenharmony_ci long rest; 8762306a36Sopenharmony_ci unsigned char buffer[25]; /* we have to bit shift 25 registers */ 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci freq = freq / 160; /* convert the freq. to a nice to handle value */ 9062306a36Sopenharmony_ci memset(buffer, 0, sizeof(buffer)); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci rest = freq * 10 + 10700; /* I once had understood what is going on here */ 9362306a36Sopenharmony_ci /* maybe some wise guy (friedhelm?) can comment this stuff */ 9462306a36Sopenharmony_ci i = 13; 9562306a36Sopenharmony_ci temp = 102400; 9662306a36Sopenharmony_ci while (rest != 0) { 9762306a36Sopenharmony_ci if (rest % temp == rest) 9862306a36Sopenharmony_ci buffer[i] = 0; 9962306a36Sopenharmony_ci else { 10062306a36Sopenharmony_ci buffer[i] = 1; 10162306a36Sopenharmony_ci rest = rest - temp; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci i--; 10462306a36Sopenharmony_ci temp = temp / 2; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */ 10862306a36Sopenharmony_ci if (buffer[i] == 1) { 10962306a36Sopenharmony_ci outb(WRT_EN | DATA, isa->io); 11062306a36Sopenharmony_ci outb(WRT_EN | DATA | CLK_ON, isa->io); 11162306a36Sopenharmony_ci outb(WRT_EN | DATA, isa->io); 11262306a36Sopenharmony_ci } else { 11362306a36Sopenharmony_ci outb(WRT_EN | 0x00, isa->io); 11462306a36Sopenharmony_ci outb(WRT_EN | 0x00 | CLK_ON, isa->io); 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci outb(0x00, isa->io); 11862306a36Sopenharmony_ci return 0; 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic u32 terratec_g_signal(struct radio_isa_card *isa) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci /* bit set = no signal present */ 12462306a36Sopenharmony_ci return (inb(isa->io) & 2) ? 0 : 0xffff; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic const struct radio_isa_ops terratec_ops = { 12862306a36Sopenharmony_ci .alloc = terratec_alloc, 12962306a36Sopenharmony_ci .s_mute_volume = terratec_s_mute_volume, 13062306a36Sopenharmony_ci .s_frequency = terratec_s_frequency, 13162306a36Sopenharmony_ci .g_signal = terratec_g_signal, 13262306a36Sopenharmony_ci}; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic const int terratec_ioports[] = { 0x590 }; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic struct radio_isa_driver terratec_driver = { 13762306a36Sopenharmony_ci .driver = { 13862306a36Sopenharmony_ci .match = radio_isa_match, 13962306a36Sopenharmony_ci .probe = radio_isa_probe, 14062306a36Sopenharmony_ci .remove = radio_isa_remove, 14162306a36Sopenharmony_ci .driver = { 14262306a36Sopenharmony_ci .name = "radio-terratec", 14362306a36Sopenharmony_ci }, 14462306a36Sopenharmony_ci }, 14562306a36Sopenharmony_ci .io_params = &io, 14662306a36Sopenharmony_ci .radio_nr_params = &radio_nr, 14762306a36Sopenharmony_ci .io_ports = terratec_ioports, 14862306a36Sopenharmony_ci .num_of_io_ports = ARRAY_SIZE(terratec_ioports), 14962306a36Sopenharmony_ci .region_size = 2, 15062306a36Sopenharmony_ci .card = "TerraTec ActiveRadio", 15162306a36Sopenharmony_ci .ops = &terratec_ops, 15262306a36Sopenharmony_ci .has_stereo = true, 15362306a36Sopenharmony_ci .max_volume = 10, 15462306a36Sopenharmony_ci}; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic int __init terratec_init(void) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci return isa_register_driver(&terratec_driver.driver, 1); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic void __exit terratec_exit(void) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci isa_unregister_driver(&terratec_driver.driver); 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cimodule_init(terratec_init); 16762306a36Sopenharmony_cimodule_exit(terratec_exit); 16862306a36Sopenharmony_ci 169