Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /* Typhoon Radio Card driver for radio support
0003  * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
0004  *
0005  * Notes on the hardware
0006  *
0007  * This card has two output sockets, one for speakers and one for line.
0008  * The speaker output has volume control, but only in four discrete
0009  * steps. The line output has neither volume control nor mute.
0010  *
0011  * The card has auto-stereo according to its manual, although it all
0012  * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
0013  * antenna - I really don't know for sure.
0014  *
0015  * Frequency control is done digitally.
0016  *
0017  * Volume control is done digitally, but there are only four different
0018  * possible values. So you should better always turn the volume up and
0019  * use line control. I got the best results by connecting line output
0020  * to the sound card microphone input. For such a configuration the
0021  * volume control has no effect, since volume control only influences
0022  * the speaker output.
0023  *
0024  * There is no explicit mute/unmute. So I set the radio frequency to a
0025  * value where I do expect just noise and turn the speaker volume down.
0026  * The frequency change is necessary since the card never seems to be
0027  * completely silent.
0028  *
0029  * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org>
0030  */
0031 
0032 #include <linux/module.h>   /* Modules                        */
0033 #include <linux/init.h>     /* Initdata                       */
0034 #include <linux/ioport.h>   /* request_region         */
0035 #include <linux/videodev2.h>    /* kernel radio structs           */
0036 #include <linux/io.h>       /* outb, outb_p                   */
0037 #include <linux/slab.h>
0038 #include <media/v4l2-device.h>
0039 #include <media/v4l2-ioctl.h>
0040 #include "radio-isa.h"
0041 
0042 #define DRIVER_VERSION "0.1.2"
0043 
0044 MODULE_AUTHOR("Dr. Henrik Seidel");
0045 MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
0046 MODULE_LICENSE("GPL");
0047 MODULE_VERSION("0.1.99");
0048 
0049 #ifndef CONFIG_RADIO_TYPHOON_PORT
0050 #define CONFIG_RADIO_TYPHOON_PORT -1
0051 #endif
0052 
0053 #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
0054 #define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000
0055 #endif
0056 
0057 #define TYPHOON_MAX 2
0058 
0059 static int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT,
0060                   [1 ... (TYPHOON_MAX - 1)] = -1 };
0061 static int radio_nr[TYPHOON_MAX]    = { [0 ... (TYPHOON_MAX - 1)] = -1 };
0062 static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ;
0063 
0064 module_param_array(io, int, NULL, 0444);
0065 MODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)");
0066 module_param_array(radio_nr, int, NULL, 0444);
0067 MODULE_PARM_DESC(radio_nr, "Radio device numbers");
0068 module_param(mutefreq, ulong, 0);
0069 MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
0070 
0071 struct typhoon {
0072     struct radio_isa_card isa;
0073     int muted;
0074 };
0075 
0076 static struct radio_isa_card *typhoon_alloc(void)
0077 {
0078     struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL);
0079 
0080     return ty ? &ty->isa : NULL;
0081 }
0082 
0083 static int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq)
0084 {
0085     unsigned long outval;
0086     unsigned long x;
0087 
0088     /*
0089      * The frequency transfer curve is not linear. The best fit I could
0090      * get is
0091      *
0092      * outval = -155 + exp((f + 15.55) * 0.057))
0093      *
0094      * where frequency f is in MHz. Since we don't have exp in the kernel,
0095      * I approximate this function by a third order polynomial.
0096      *
0097      */
0098 
0099     x = freq / 160;
0100     outval = (x * x + 2500) / 5000;
0101     outval = (outval * x + 5000) / 10000;
0102     outval -= (10 * x * x + 10433) / 20866;
0103     outval += 4 * x - 11505;
0104 
0105     outb_p((outval >> 8) & 0x01, isa->io + 4);
0106     outb_p(outval >> 9, isa->io + 6);
0107     outb_p(outval & 0xff, isa->io + 8);
0108     return 0;
0109 }
0110 
0111 static int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
0112 {
0113     struct typhoon *ty = container_of(isa, struct typhoon, isa);
0114 
0115     if (mute)
0116         vol = 0;
0117     vol >>= 14;         /* Map 16 bit to 2 bit */
0118     vol &= 3;
0119     outb_p(vol / 2, isa->io);   /* Set the volume, high bit. */
0120     outb_p(vol % 2, isa->io + 2);   /* Set the volume, low bit. */
0121 
0122     if (vol == 0 && !ty->muted) {
0123         ty->muted = true;
0124         return typhoon_s_frequency(isa, mutefreq << 4);
0125     }
0126     if (vol && ty->muted) {
0127         ty->muted = false;
0128         return typhoon_s_frequency(isa, isa->freq);
0129     }
0130     return 0;
0131 }
0132 
0133 static const struct radio_isa_ops typhoon_ops = {
0134     .alloc = typhoon_alloc,
0135     .s_mute_volume = typhoon_s_mute_volume,
0136     .s_frequency = typhoon_s_frequency,
0137 };
0138 
0139 static const int typhoon_ioports[] = { 0x316, 0x336 };
0140 
0141 static struct radio_isa_driver typhoon_driver = {
0142     .driver = {
0143         .match      = radio_isa_match,
0144         .probe      = radio_isa_probe,
0145         .remove     = radio_isa_remove,
0146         .driver     = {
0147             .name   = "radio-typhoon",
0148         },
0149     },
0150     .io_params = io,
0151     .radio_nr_params = radio_nr,
0152     .io_ports = typhoon_ioports,
0153     .num_of_io_ports = ARRAY_SIZE(typhoon_ioports),
0154     .region_size = 8,
0155     .card = "Typhoon Radio",
0156     .ops = &typhoon_ops,
0157     .has_stereo = true,
0158     .max_volume = 3,
0159 };
0160 
0161 static int __init typhoon_init(void)
0162 {
0163     if (mutefreq < 87000 || mutefreq > 108000) {
0164         printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n",
0165                 typhoon_driver.driver.driver.name);
0166         printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n",
0167                 typhoon_driver.driver.driver.name);
0168         return -ENODEV;
0169     }
0170     return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX);
0171 }
0172 
0173 static void __exit typhoon_exit(void)
0174 {
0175     isa_unregister_driver(&typhoon_driver.driver);
0176 }
0177 
0178 
0179 module_init(typhoon_init);
0180 module_exit(typhoon_exit);
0181