diff options
Diffstat (limited to 'drivers/media/radio/radio-zoltrix.c')
-rw-r--r-- | drivers/media/radio/radio-zoltrix.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c new file mode 100644 index 000000000..f3dc57c75 --- /dev/null +++ b/drivers/media/radio/radio-zoltrix.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Zoltrix Radio Plus driver + * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za> + * + * BUGS + * Due to the inconsistency in reading from the signal flags + * it is difficult to get an accurate tuned signal. + * + * It seems that the card is not linear to 0 volume. It cuts off + * at a low volume, and it is not possible (at least I have not found) + * to get fine volume control over the low volume range. + * + * Some code derived from code by Romolo Manfredini + * romolo@bicnet.it + * + * 1999-05-06 - (C. van Schaik) + * - Make signal strength and stereo scans + * kinder to cpu while in delay + * 1999-01-05 - (C. van Schaik) + * - Changed tuning to 1/160Mhz accuracy + * - Added stereo support + * (card defaults to stereo) + * (can explicitly force mono on the card) + * (can detect if station is in stereo) + * - Added unmute function + * - Reworked ioctl functions + * 2002-07-15 - Fix Stereo typo + * + * 2006-07-24 - Converted to V4L2 API + * by Mauro Carvalho Chehab <mchehab@kernel.org> + * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> + * + * Note that this is the driver for the Zoltrix Radio Plus. + * This driver does not work for the Zoltrix Radio Plus 108 or the + * Zoltrix Radio Plus for Windows. + * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* request_region */ +#include <linux/delay.h> /* udelay, msleep */ +#include <linux/videodev2.h> /* kernel radio structs */ +#include <linux/mutex.h> +#include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include "radio-isa.h" + +MODULE_AUTHOR("C. van Schaik"); +MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1.99"); + +#ifndef CONFIG_RADIO_ZOLTRIX_PORT +#define CONFIG_RADIO_ZOLTRIX_PORT -1 +#endif + +#define ZOLTRIX_MAX 2 + +static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT, + [1 ... (ZOLTRIX_MAX - 1)] = -1 }; +static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 }; + +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + +struct zoltrix { + struct radio_isa_card isa; + int curvol; + bool muted; +}; + +static struct radio_isa_card *zoltrix_alloc(void) +{ + struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL); + + return zol ? &zol->isa : NULL; +} + +static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) +{ + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + + zol->curvol = vol; + zol->muted = mute; + if (mute || vol == 0) { + outb(0, isa->io); + outb(0, isa->io); + inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ + return 0; + } + + outb(vol - 1, isa->io); + msleep(10); + inb(isa->io + 2); + return 0; +} + +/* tunes the radio to the desired frequency */ +static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq) +{ + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + struct v4l2_device *v4l2_dev = &isa->v4l2_dev; + unsigned long long bitmask, f, m; + bool stereo = isa->stereo; + int i; + + if (freq == 0) { + v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n"); + return -EINVAL; + } + + m = (freq / 160 - 8800) * 2; + f = (unsigned long long)m + 0x4d1c; + + bitmask = 0xc480402c10080000ull; + i = 45; + + outb(0, isa->io); + outb(0, isa->io); + inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ + + outb(0x40, isa->io); + outb(0xc0, isa->io); + + bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31)); + while (i--) { + if ((bitmask & 0x8000000000000000ull) != 0) { + outb(0x80, isa->io); + udelay(50); + outb(0x00, isa->io); + udelay(50); + outb(0x80, isa->io); + udelay(50); + } else { + outb(0xc0, isa->io); + udelay(50); + outb(0x40, isa->io); + udelay(50); + outb(0xc0, isa->io); + udelay(50); + } + bitmask *= 2; + } + /* termination sequence */ + outb(0x80, isa->io); + outb(0xc0, isa->io); + outb(0x40, isa->io); + udelay(1000); + inb(isa->io + 2); + udelay(1000); + + return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol); +} + +/* Get signal strength */ +static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa) +{ + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + int a, b; + + outb(0x00, isa->io); /* This stuff I found to do nothing */ + outb(zol->curvol, isa->io); + msleep(20); + + a = inb(isa->io); + msleep(10); + b = inb(isa->io); + + return (a == b && a == 0xcf) ? + V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; +} + +static u32 zoltrix_g_signal(struct radio_isa_card *isa) +{ + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + int a, b; + + outb(0x00, isa->io); /* This stuff I found to do nothing */ + outb(zol->curvol, isa->io); + msleep(20); + + a = inb(isa->io); + msleep(10); + b = inb(isa->io); + + if (a != b) + return 0; + + /* I found this out by playing with a binary scanner on the card io */ + return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0; +} + +static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo) +{ + return zoltrix_s_frequency(isa, isa->freq); +} + +static const struct radio_isa_ops zoltrix_ops = { + .alloc = zoltrix_alloc, + .s_mute_volume = zoltrix_s_mute_volume, + .s_frequency = zoltrix_s_frequency, + .s_stereo = zoltrix_s_stereo, + .g_rxsubchans = zoltrix_g_rxsubchans, + .g_signal = zoltrix_g_signal, +}; + +static const int zoltrix_ioports[] = { 0x20c, 0x30c }; + +static struct radio_isa_driver zoltrix_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-zoltrix", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = zoltrix_ioports, + .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports), + .region_size = 2, + .card = "Zoltrix Radio Plus", + .ops = &zoltrix_ops, + .has_stereo = true, + .max_volume = 15, +}; + +static int __init zoltrix_init(void) +{ + return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX); +} + +static void __exit zoltrix_exit(void) +{ + isa_unregister_driver(&zoltrix_driver.driver); +} + +module_init(zoltrix_init); +module_exit(zoltrix_exit); + |