18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Zoltrix Radio Plus driver 48c2ecf20Sopenharmony_ci * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * BUGS 78c2ecf20Sopenharmony_ci * Due to the inconsistency in reading from the signal flags 88c2ecf20Sopenharmony_ci * it is difficult to get an accurate tuned signal. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * It seems that the card is not linear to 0 volume. It cuts off 118c2ecf20Sopenharmony_ci * at a low volume, and it is not possible (at least I have not found) 128c2ecf20Sopenharmony_ci * to get fine volume control over the low volume range. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * Some code derived from code by Romolo Manfredini 158c2ecf20Sopenharmony_ci * romolo@bicnet.it 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * 1999-05-06 - (C. van Schaik) 188c2ecf20Sopenharmony_ci * - Make signal strength and stereo scans 198c2ecf20Sopenharmony_ci * kinder to cpu while in delay 208c2ecf20Sopenharmony_ci * 1999-01-05 - (C. van Schaik) 218c2ecf20Sopenharmony_ci * - Changed tuning to 1/160Mhz accuracy 228c2ecf20Sopenharmony_ci * - Added stereo support 238c2ecf20Sopenharmony_ci * (card defaults to stereo) 248c2ecf20Sopenharmony_ci * (can explicitly force mono on the card) 258c2ecf20Sopenharmony_ci * (can detect if station is in stereo) 268c2ecf20Sopenharmony_ci * - Added unmute function 278c2ecf20Sopenharmony_ci * - Reworked ioctl functions 288c2ecf20Sopenharmony_ci * 2002-07-15 - Fix Stereo typo 298c2ecf20Sopenharmony_ci * 308c2ecf20Sopenharmony_ci * 2006-07-24 - Converted to V4L2 API 318c2ecf20Sopenharmony_ci * by Mauro Carvalho Chehab <mchehab@kernel.org> 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * Note that this is the driver for the Zoltrix Radio Plus. 368c2ecf20Sopenharmony_ci * This driver does not work for the Zoltrix Radio Plus 108 or the 378c2ecf20Sopenharmony_ci * Zoltrix Radio Plus for Windows. 388c2ecf20Sopenharmony_ci * 398c2ecf20Sopenharmony_ci * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#include <linux/module.h> /* Modules */ 438c2ecf20Sopenharmony_ci#include <linux/init.h> /* Initdata */ 448c2ecf20Sopenharmony_ci#include <linux/ioport.h> /* request_region */ 458c2ecf20Sopenharmony_ci#include <linux/delay.h> /* udelay, msleep */ 468c2ecf20Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 478c2ecf20Sopenharmony_ci#include <linux/mutex.h> 488c2ecf20Sopenharmony_ci#include <linux/io.h> /* outb, outb_p */ 498c2ecf20Sopenharmony_ci#include <linux/slab.h> 508c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 518c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 528c2ecf20Sopenharmony_ci#include "radio-isa.h" 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ciMODULE_AUTHOR("C. van Schaik"); 558c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); 568c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 578c2ecf20Sopenharmony_ciMODULE_VERSION("0.1.99"); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci#ifndef CONFIG_RADIO_ZOLTRIX_PORT 608c2ecf20Sopenharmony_ci#define CONFIG_RADIO_ZOLTRIX_PORT -1 618c2ecf20Sopenharmony_ci#endif 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci#define ZOLTRIX_MAX 2 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT, 668c2ecf20Sopenharmony_ci [1 ... (ZOLTRIX_MAX - 1)] = -1 }; 678c2ecf20Sopenharmony_cistatic int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 }; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cimodule_param_array(io, int, NULL, 0444); 708c2ecf20Sopenharmony_ciMODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)"); 718c2ecf20Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444); 728c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device numbers"); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistruct zoltrix { 758c2ecf20Sopenharmony_ci struct radio_isa_card isa; 768c2ecf20Sopenharmony_ci int curvol; 778c2ecf20Sopenharmony_ci bool muted; 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic struct radio_isa_card *zoltrix_alloc(void) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci return zol ? &zol->isa : NULL; 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci zol->curvol = vol; 928c2ecf20Sopenharmony_ci zol->muted = mute; 938c2ecf20Sopenharmony_ci if (mute || vol == 0) { 948c2ecf20Sopenharmony_ci outb(0, isa->io); 958c2ecf20Sopenharmony_ci outb(0, isa->io); 968c2ecf20Sopenharmony_ci inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci outb(vol - 1, isa->io); 1018c2ecf20Sopenharmony_ci msleep(10); 1028c2ecf20Sopenharmony_ci inb(isa->io + 2); 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* tunes the radio to the desired frequency */ 1078c2ecf20Sopenharmony_cistatic int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 1108c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = &isa->v4l2_dev; 1118c2ecf20Sopenharmony_ci unsigned long long bitmask, f, m; 1128c2ecf20Sopenharmony_ci bool stereo = isa->stereo; 1138c2ecf20Sopenharmony_ci int i; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (freq == 0) { 1168c2ecf20Sopenharmony_ci v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n"); 1178c2ecf20Sopenharmony_ci return -EINVAL; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci m = (freq / 160 - 8800) * 2; 1218c2ecf20Sopenharmony_ci f = (unsigned long long)m + 0x4d1c; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci bitmask = 0xc480402c10080000ull; 1248c2ecf20Sopenharmony_ci i = 45; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci outb(0, isa->io); 1278c2ecf20Sopenharmony_ci outb(0, isa->io); 1288c2ecf20Sopenharmony_ci inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci outb(0x40, isa->io); 1318c2ecf20Sopenharmony_ci outb(0xc0, isa->io); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31)); 1348c2ecf20Sopenharmony_ci while (i--) { 1358c2ecf20Sopenharmony_ci if ((bitmask & 0x8000000000000000ull) != 0) { 1368c2ecf20Sopenharmony_ci outb(0x80, isa->io); 1378c2ecf20Sopenharmony_ci udelay(50); 1388c2ecf20Sopenharmony_ci outb(0x00, isa->io); 1398c2ecf20Sopenharmony_ci udelay(50); 1408c2ecf20Sopenharmony_ci outb(0x80, isa->io); 1418c2ecf20Sopenharmony_ci udelay(50); 1428c2ecf20Sopenharmony_ci } else { 1438c2ecf20Sopenharmony_ci outb(0xc0, isa->io); 1448c2ecf20Sopenharmony_ci udelay(50); 1458c2ecf20Sopenharmony_ci outb(0x40, isa->io); 1468c2ecf20Sopenharmony_ci udelay(50); 1478c2ecf20Sopenharmony_ci outb(0xc0, isa->io); 1488c2ecf20Sopenharmony_ci udelay(50); 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci bitmask *= 2; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci /* termination sequence */ 1538c2ecf20Sopenharmony_ci outb(0x80, isa->io); 1548c2ecf20Sopenharmony_ci outb(0xc0, isa->io); 1558c2ecf20Sopenharmony_ci outb(0x40, isa->io); 1568c2ecf20Sopenharmony_ci udelay(1000); 1578c2ecf20Sopenharmony_ci inb(isa->io + 2); 1588c2ecf20Sopenharmony_ci udelay(1000); 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol); 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci/* Get signal strength */ 1648c2ecf20Sopenharmony_cistatic u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 1678c2ecf20Sopenharmony_ci int a, b; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci outb(0x00, isa->io); /* This stuff I found to do nothing */ 1708c2ecf20Sopenharmony_ci outb(zol->curvol, isa->io); 1718c2ecf20Sopenharmony_ci msleep(20); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci a = inb(isa->io); 1748c2ecf20Sopenharmony_ci msleep(10); 1758c2ecf20Sopenharmony_ci b = inb(isa->io); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci return (a == b && a == 0xcf) ? 1788c2ecf20Sopenharmony_ci V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic u32 zoltrix_g_signal(struct radio_isa_card *isa) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 1848c2ecf20Sopenharmony_ci int a, b; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci outb(0x00, isa->io); /* This stuff I found to do nothing */ 1878c2ecf20Sopenharmony_ci outb(zol->curvol, isa->io); 1888c2ecf20Sopenharmony_ci msleep(20); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci a = inb(isa->io); 1918c2ecf20Sopenharmony_ci msleep(10); 1928c2ecf20Sopenharmony_ci b = inb(isa->io); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (a != b) 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci /* I found this out by playing with a binary scanner on the card io */ 1988c2ecf20Sopenharmony_ci return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo) 2028c2ecf20Sopenharmony_ci{ 2038c2ecf20Sopenharmony_ci return zoltrix_s_frequency(isa, isa->freq); 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic const struct radio_isa_ops zoltrix_ops = { 2078c2ecf20Sopenharmony_ci .alloc = zoltrix_alloc, 2088c2ecf20Sopenharmony_ci .s_mute_volume = zoltrix_s_mute_volume, 2098c2ecf20Sopenharmony_ci .s_frequency = zoltrix_s_frequency, 2108c2ecf20Sopenharmony_ci .s_stereo = zoltrix_s_stereo, 2118c2ecf20Sopenharmony_ci .g_rxsubchans = zoltrix_g_rxsubchans, 2128c2ecf20Sopenharmony_ci .g_signal = zoltrix_g_signal, 2138c2ecf20Sopenharmony_ci}; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic const int zoltrix_ioports[] = { 0x20c, 0x30c }; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic struct radio_isa_driver zoltrix_driver = { 2188c2ecf20Sopenharmony_ci .driver = { 2198c2ecf20Sopenharmony_ci .match = radio_isa_match, 2208c2ecf20Sopenharmony_ci .probe = radio_isa_probe, 2218c2ecf20Sopenharmony_ci .remove = radio_isa_remove, 2228c2ecf20Sopenharmony_ci .driver = { 2238c2ecf20Sopenharmony_ci .name = "radio-zoltrix", 2248c2ecf20Sopenharmony_ci }, 2258c2ecf20Sopenharmony_ci }, 2268c2ecf20Sopenharmony_ci .io_params = io, 2278c2ecf20Sopenharmony_ci .radio_nr_params = radio_nr, 2288c2ecf20Sopenharmony_ci .io_ports = zoltrix_ioports, 2298c2ecf20Sopenharmony_ci .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports), 2308c2ecf20Sopenharmony_ci .region_size = 2, 2318c2ecf20Sopenharmony_ci .card = "Zoltrix Radio Plus", 2328c2ecf20Sopenharmony_ci .ops = &zoltrix_ops, 2338c2ecf20Sopenharmony_ci .has_stereo = true, 2348c2ecf20Sopenharmony_ci .max_volume = 15, 2358c2ecf20Sopenharmony_ci}; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic int __init zoltrix_init(void) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX); 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic void __exit zoltrix_exit(void) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci isa_unregister_driver(&zoltrix_driver.driver); 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_cimodule_init(zoltrix_init); 2488c2ecf20Sopenharmony_cimodule_exit(zoltrix_exit); 2498c2ecf20Sopenharmony_ci 250