radio-zoltrix.c (6476B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Zoltrix Radio Plus driver 4 * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za> 5 * 6 * BUGS 7 * Due to the inconsistency in reading from the signal flags 8 * it is difficult to get an accurate tuned signal. 9 * 10 * It seems that the card is not linear to 0 volume. It cuts off 11 * at a low volume, and it is not possible (at least I have not found) 12 * to get fine volume control over the low volume range. 13 * 14 * Some code derived from code by Romolo Manfredini 15 * romolo@bicnet.it 16 * 17 * 1999-05-06 - (C. van Schaik) 18 * - Make signal strength and stereo scans 19 * kinder to cpu while in delay 20 * 1999-01-05 - (C. van Schaik) 21 * - Changed tuning to 1/160Mhz accuracy 22 * - Added stereo support 23 * (card defaults to stereo) 24 * (can explicitly force mono on the card) 25 * (can detect if station is in stereo) 26 * - Added unmute function 27 * - Reworked ioctl functions 28 * 2002-07-15 - Fix Stereo typo 29 * 30 * 2006-07-24 - Converted to V4L2 API 31 * by Mauro Carvalho Chehab <mchehab@kernel.org> 32 * 33 * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> 34 * 35 * Note that this is the driver for the Zoltrix Radio Plus. 36 * This driver does not work for the Zoltrix Radio Plus 108 or the 37 * Zoltrix Radio Plus for Windows. 38 * 39 * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. 40 */ 41 42#include <linux/module.h> /* Modules */ 43#include <linux/init.h> /* Initdata */ 44#include <linux/ioport.h> /* request_region */ 45#include <linux/delay.h> /* udelay, msleep */ 46#include <linux/videodev2.h> /* kernel radio structs */ 47#include <linux/mutex.h> 48#include <linux/io.h> /* outb, outb_p */ 49#include <linux/slab.h> 50#include <media/v4l2-device.h> 51#include <media/v4l2-ioctl.h> 52#include "radio-isa.h" 53 54MODULE_AUTHOR("C. van Schaik"); 55MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); 56MODULE_LICENSE("GPL"); 57MODULE_VERSION("0.1.99"); 58 59#ifndef CONFIG_RADIO_ZOLTRIX_PORT 60#define CONFIG_RADIO_ZOLTRIX_PORT -1 61#endif 62 63#define ZOLTRIX_MAX 2 64 65static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT, 66 [1 ... (ZOLTRIX_MAX - 1)] = -1 }; 67static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 }; 68 69module_param_array(io, int, NULL, 0444); 70MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)"); 71module_param_array(radio_nr, int, NULL, 0444); 72MODULE_PARM_DESC(radio_nr, "Radio device numbers"); 73 74struct zoltrix { 75 struct radio_isa_card isa; 76 int curvol; 77 bool muted; 78}; 79 80static struct radio_isa_card *zoltrix_alloc(void) 81{ 82 struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL); 83 84 return zol ? &zol->isa : NULL; 85} 86 87static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) 88{ 89 struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 90 91 zol->curvol = vol; 92 zol->muted = mute; 93 if (mute || vol == 0) { 94 outb(0, isa->io); 95 outb(0, isa->io); 96 inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ 97 return 0; 98 } 99 100 outb(vol - 1, isa->io); 101 msleep(10); 102 inb(isa->io + 2); 103 return 0; 104} 105 106/* tunes the radio to the desired frequency */ 107static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq) 108{ 109 struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 110 struct v4l2_device *v4l2_dev = &isa->v4l2_dev; 111 unsigned long long bitmask, f, m; 112 bool stereo = isa->stereo; 113 int i; 114 115 if (freq == 0) { 116 v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n"); 117 return -EINVAL; 118 } 119 120 m = (freq / 160 - 8800) * 2; 121 f = (unsigned long long)m + 0x4d1c; 122 123 bitmask = 0xc480402c10080000ull; 124 i = 45; 125 126 outb(0, isa->io); 127 outb(0, isa->io); 128 inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ 129 130 outb(0x40, isa->io); 131 outb(0xc0, isa->io); 132 133 bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31)); 134 while (i--) { 135 if ((bitmask & 0x8000000000000000ull) != 0) { 136 outb(0x80, isa->io); 137 udelay(50); 138 outb(0x00, isa->io); 139 udelay(50); 140 outb(0x80, isa->io); 141 udelay(50); 142 } else { 143 outb(0xc0, isa->io); 144 udelay(50); 145 outb(0x40, isa->io); 146 udelay(50); 147 outb(0xc0, isa->io); 148 udelay(50); 149 } 150 bitmask *= 2; 151 } 152 /* termination sequence */ 153 outb(0x80, isa->io); 154 outb(0xc0, isa->io); 155 outb(0x40, isa->io); 156 udelay(1000); 157 inb(isa->io + 2); 158 udelay(1000); 159 160 return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol); 161} 162 163/* Get signal strength */ 164static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa) 165{ 166 struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 167 int a, b; 168 169 outb(0x00, isa->io); /* This stuff I found to do nothing */ 170 outb(zol->curvol, isa->io); 171 msleep(20); 172 173 a = inb(isa->io); 174 msleep(10); 175 b = inb(isa->io); 176 177 return (a == b && a == 0xcf) ? 178 V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 179} 180 181static u32 zoltrix_g_signal(struct radio_isa_card *isa) 182{ 183 struct zoltrix *zol = container_of(isa, struct zoltrix, isa); 184 int a, b; 185 186 outb(0x00, isa->io); /* This stuff I found to do nothing */ 187 outb(zol->curvol, isa->io); 188 msleep(20); 189 190 a = inb(isa->io); 191 msleep(10); 192 b = inb(isa->io); 193 194 if (a != b) 195 return 0; 196 197 /* I found this out by playing with a binary scanner on the card io */ 198 return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0; 199} 200 201static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo) 202{ 203 return zoltrix_s_frequency(isa, isa->freq); 204} 205 206static const struct radio_isa_ops zoltrix_ops = { 207 .alloc = zoltrix_alloc, 208 .s_mute_volume = zoltrix_s_mute_volume, 209 .s_frequency = zoltrix_s_frequency, 210 .s_stereo = zoltrix_s_stereo, 211 .g_rxsubchans = zoltrix_g_rxsubchans, 212 .g_signal = zoltrix_g_signal, 213}; 214 215static const int zoltrix_ioports[] = { 0x20c, 0x30c }; 216 217static struct radio_isa_driver zoltrix_driver = { 218 .driver = { 219 .match = radio_isa_match, 220 .probe = radio_isa_probe, 221 .remove = radio_isa_remove, 222 .driver = { 223 .name = "radio-zoltrix", 224 }, 225 }, 226 .io_params = io, 227 .radio_nr_params = radio_nr, 228 .io_ports = zoltrix_ioports, 229 .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports), 230 .region_size = 2, 231 .card = "Zoltrix Radio Plus", 232 .ops = &zoltrix_ops, 233 .has_stereo = true, 234 .max_volume = 15, 235}; 236 237static int __init zoltrix_init(void) 238{ 239 return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX); 240} 241 242static void __exit zoltrix_exit(void) 243{ 244 isa_unregister_driver(&zoltrix_driver.driver); 245} 246 247module_init(zoltrix_init); 248module_exit(zoltrix_exit); 249