250 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Zoltrix Radio Plus driver4 * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za>5 *6 * BUGS7 * Due to the inconsistency in reading from the signal flags8 * it is difficult to get an accurate tuned signal.9 *10 * It seems that the card is not linear to 0 volume. It cuts off11 * 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 Manfredini15 * romolo@bicnet.it16 *17 * 1999-05-06 - (C. van Schaik)18 * - Make signal strength and stereo scans19 * kinder to cpu while in delay20 * 1999-01-05 - (C. van Schaik)21 * - Changed tuning to 1/160Mhz accuracy22 * - Added stereo support23 * (card defaults to stereo)24 * (can explicitly force mono on the card)25 * (can detect if station is in stereo)26 * - Added unmute function27 * - Reworked ioctl functions28 * 2002-07-15 - Fix Stereo typo29 *30 * 2006-07-24 - Converted to V4L2 API31 * 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 the37 * 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_PORT60#define CONFIG_RADIO_ZOLTRIX_PORT -161#endif62 63#define ZOLTRIX_MAX 264 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 250