162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Zoltrix Radio Plus driver 462306a36Sopenharmony_ci * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * BUGS 762306a36Sopenharmony_ci * Due to the inconsistency in reading from the signal flags 862306a36Sopenharmony_ci * it is difficult to get an accurate tuned signal. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * It seems that the card is not linear to 0 volume. It cuts off 1162306a36Sopenharmony_ci * at a low volume, and it is not possible (at least I have not found) 1262306a36Sopenharmony_ci * to get fine volume control over the low volume range. 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * Some code derived from code by Romolo Manfredini 1562306a36Sopenharmony_ci * romolo@bicnet.it 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * 1999-05-06 - (C. van Schaik) 1862306a36Sopenharmony_ci * - Make signal strength and stereo scans 1962306a36Sopenharmony_ci * kinder to cpu while in delay 2062306a36Sopenharmony_ci * 1999-01-05 - (C. van Schaik) 2162306a36Sopenharmony_ci * - Changed tuning to 1/160Mhz accuracy 2262306a36Sopenharmony_ci * - Added stereo support 2362306a36Sopenharmony_ci * (card defaults to stereo) 2462306a36Sopenharmony_ci * (can explicitly force mono on the card) 2562306a36Sopenharmony_ci * (can detect if station is in stereo) 2662306a36Sopenharmony_ci * - Added unmute function 2762306a36Sopenharmony_ci * - Reworked ioctl functions 2862306a36Sopenharmony_ci * 2002-07-15 - Fix Stereo typo 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * 2006-07-24 - Converted to V4L2 API 3162306a36Sopenharmony_ci * by Mauro Carvalho Chehab <mchehab@kernel.org> 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> 3462306a36Sopenharmony_ci * 3562306a36Sopenharmony_ci * Note that this is the driver for the Zoltrix Radio Plus. 3662306a36Sopenharmony_ci * This driver does not work for the Zoltrix Radio Plus 108 or the 3762306a36Sopenharmony_ci * Zoltrix Radio Plus for Windows. 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. 4062306a36Sopenharmony_ci */ 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#include <linux/module.h> /* Modules */ 4362306a36Sopenharmony_ci#include <linux/init.h> /* Initdata */ 4462306a36Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 4562306a36Sopenharmony_ci#include <linux/delay.h> /* udelay, msleep */ 4662306a36Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 4762306a36Sopenharmony_ci#include <linux/mutex.h> 4862306a36Sopenharmony_ci#include <linux/io.h> /* outb, outb_p */ 4962306a36Sopenharmony_ci#include <linux/slab.h> 5062306a36Sopenharmony_ci#include <media/v4l2-device.h> 5162306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 5262306a36Sopenharmony_ci#include "radio-isa.h" 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ciMODULE_AUTHOR("C. van Schaik"); 5562306a36Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); 5662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 5762306a36Sopenharmony_ciMODULE_VERSION("0.1.99"); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci#ifndef CONFIG_RADIO_ZOLTRIX_PORT 6062306a36Sopenharmony_ci#define CONFIG_RADIO_ZOLTRIX_PORT -1 6162306a36Sopenharmony_ci#endif 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#define ZOLTRIX_MAX 2 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT, 6662306a36Sopenharmony_ci [1 ... (ZOLTRIX_MAX - 1)] = -1 }; 6762306a36Sopenharmony_cistatic int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 }; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cimodule_param_array(io, int, NULL, 0444); 7062306a36Sopenharmony_ciMODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)"); 7162306a36Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444); 7262306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device numbers"); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistruct zoltrix { 7562306a36Sopenharmony_ci struct radio_isa_card isa; 7662306a36Sopenharmony_ci int curvol; 7762306a36Sopenharmony_ci bool muted; 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic struct radio_isa_card *zoltrix_alloc(void) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci return zol ? &zol->isa : NULL; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci zol->curvol = vol; 9262306a36Sopenharmony_ci zol->muted = mute; 9362306a36Sopenharmony_ci if (mute || vol == 0) { 9462306a36Sopenharmony_ci outb(0, isa->io); 9562306a36Sopenharmony_ci outb(0, isa->io); 9662306a36Sopenharmony_ci inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ 9762306a36Sopenharmony_ci return 0; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci outb(vol - 1, isa->io); 10162306a36Sopenharmony_ci msleep(10); 10262306a36Sopenharmony_ci inb(isa->io + 2); 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/* tunes the radio to the desired frequency */ 10762306a36Sopenharmony_cistatic int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 11062306a36Sopenharmony_ci struct v4l2_device *v4l2_dev = &isa->v4l2_dev; 11162306a36Sopenharmony_ci unsigned long long bitmask, f, m; 11262306a36Sopenharmony_ci bool stereo = isa->stereo; 11362306a36Sopenharmony_ci int i; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (freq == 0) { 11662306a36Sopenharmony_ci v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n"); 11762306a36Sopenharmony_ci return -EINVAL; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci m = (freq / 160 - 8800) * 2; 12162306a36Sopenharmony_ci f = (unsigned long long)m + 0x4d1c; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci bitmask = 0xc480402c10080000ull; 12462306a36Sopenharmony_ci i = 45; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci outb(0, isa->io); 12762306a36Sopenharmony_ci outb(0, isa->io); 12862306a36Sopenharmony_ci inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci outb(0x40, isa->io); 13162306a36Sopenharmony_ci outb(0xc0, isa->io); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31)); 13462306a36Sopenharmony_ci while (i--) { 13562306a36Sopenharmony_ci if ((bitmask & 0x8000000000000000ull) != 0) { 13662306a36Sopenharmony_ci outb(0x80, isa->io); 13762306a36Sopenharmony_ci udelay(50); 13862306a36Sopenharmony_ci outb(0x00, isa->io); 13962306a36Sopenharmony_ci udelay(50); 14062306a36Sopenharmony_ci outb(0x80, isa->io); 14162306a36Sopenharmony_ci udelay(50); 14262306a36Sopenharmony_ci } else { 14362306a36Sopenharmony_ci outb(0xc0, isa->io); 14462306a36Sopenharmony_ci udelay(50); 14562306a36Sopenharmony_ci outb(0x40, isa->io); 14662306a36Sopenharmony_ci udelay(50); 14762306a36Sopenharmony_ci outb(0xc0, isa->io); 14862306a36Sopenharmony_ci udelay(50); 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci bitmask *= 2; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci /* termination sequence */ 15362306a36Sopenharmony_ci outb(0x80, isa->io); 15462306a36Sopenharmony_ci outb(0xc0, isa->io); 15562306a36Sopenharmony_ci outb(0x40, isa->io); 15662306a36Sopenharmony_ci udelay(1000); 15762306a36Sopenharmony_ci inb(isa->io + 2); 15862306a36Sopenharmony_ci udelay(1000); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci/* Get signal strength */ 16462306a36Sopenharmony_cistatic u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 16762306a36Sopenharmony_ci int a, b; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci outb(0x00, isa->io); /* This stuff I found to do nothing */ 17062306a36Sopenharmony_ci outb(zol->curvol, isa->io); 17162306a36Sopenharmony_ci msleep(20); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci a = inb(isa->io); 17462306a36Sopenharmony_ci msleep(10); 17562306a36Sopenharmony_ci b = inb(isa->io); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return (a == b && a == 0xcf) ? 17862306a36Sopenharmony_ci V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic u32 zoltrix_g_signal(struct radio_isa_card *isa) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 18462306a36Sopenharmony_ci int a, b; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci outb(0x00, isa->io); /* This stuff I found to do nothing */ 18762306a36Sopenharmony_ci outb(zol->curvol, isa->io); 18862306a36Sopenharmony_ci msleep(20); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci a = inb(isa->io); 19162306a36Sopenharmony_ci msleep(10); 19262306a36Sopenharmony_ci b = inb(isa->io); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (a != b) 19562306a36Sopenharmony_ci return 0; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* I found this out by playing with a binary scanner on the card io */ 19862306a36Sopenharmony_ci return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci return zoltrix_s_frequency(isa, isa->freq); 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic const struct radio_isa_ops zoltrix_ops = { 20762306a36Sopenharmony_ci .alloc = zoltrix_alloc, 20862306a36Sopenharmony_ci .s_mute_volume = zoltrix_s_mute_volume, 20962306a36Sopenharmony_ci .s_frequency = zoltrix_s_frequency, 21062306a36Sopenharmony_ci .s_stereo = zoltrix_s_stereo, 21162306a36Sopenharmony_ci .g_rxsubchans = zoltrix_g_rxsubchans, 21262306a36Sopenharmony_ci .g_signal = zoltrix_g_signal, 21362306a36Sopenharmony_ci}; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic const int zoltrix_ioports[] = { 0x20c, 0x30c }; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic struct radio_isa_driver zoltrix_driver = { 21862306a36Sopenharmony_ci .driver = { 21962306a36Sopenharmony_ci .match = radio_isa_match, 22062306a36Sopenharmony_ci .probe = radio_isa_probe, 22162306a36Sopenharmony_ci .remove = radio_isa_remove, 22262306a36Sopenharmony_ci .driver = { 22362306a36Sopenharmony_ci .name = "radio-zoltrix", 22462306a36Sopenharmony_ci }, 22562306a36Sopenharmony_ci }, 22662306a36Sopenharmony_ci .io_params = io, 22762306a36Sopenharmony_ci .radio_nr_params = radio_nr, 22862306a36Sopenharmony_ci .io_ports = zoltrix_ioports, 22962306a36Sopenharmony_ci .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports), 23062306a36Sopenharmony_ci .region_size = 2, 23162306a36Sopenharmony_ci .card = "Zoltrix Radio Plus", 23262306a36Sopenharmony_ci .ops = &zoltrix_ops, 23362306a36Sopenharmony_ci .has_stereo = true, 23462306a36Sopenharmony_ci .max_volume = 15, 23562306a36Sopenharmony_ci}; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic int __init zoltrix_init(void) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX); 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic void __exit zoltrix_exit(void) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci isa_unregister_driver(&zoltrix_driver.driver); 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_cimodule_init(zoltrix_init); 24862306a36Sopenharmony_cimodule_exit(zoltrix_exit); 24962306a36Sopenharmony_ci 250