^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0-only
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * radio-aztech.c - Aztech radio card driver
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) * Adapted to support the Video for Linux API by
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) * Quay Ly
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) * Donald Song
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) #include <linux/module.h> /* Modules */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) #include <linux/init.h> /* Initdata */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) #include <linux/ioport.h> /* request_region */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) #include <linux/delay.h> /* udelay */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) #include <linux/videodev2.h> /* kernel radio structs */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) #include <linux/io.h> /* outb, outb_p */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) #include <linux/slab.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) #include <media/v4l2-device.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) #include <media/v4l2-ioctl.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) #include <media/v4l2-ctrls.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) #include "radio-isa.h"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) #include "lm7000.h"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) MODULE_DESCRIPTION("A driver for the Aztech radio card.");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) MODULE_LICENSE("GPL");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) MODULE_VERSION("1.0.0");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) #ifndef CONFIG_RADIO_AZTECH_PORT
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) #define CONFIG_RADIO_AZTECH_PORT -1
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) #endif
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) #define AZTECH_MAX 2
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) [1 ... (AZTECH_MAX - 1)] = -1 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) module_param_array(io, int, NULL, 0444);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) module_param_array(radio_nr, int, NULL, 0444);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) MODULE_PARM_DESC(radio_nr, "Radio device numbers");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) struct aztech {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) struct radio_isa_card isa;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) int curvol;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) /* bit definitions for register read */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) #define AZTECH_BIT_NOT_TUNED (1 << 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) #define AZTECH_BIT_MONO (1 << 1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) /* bit definitions for register write */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) #define AZTECH_BIT_TUN_CE (1 << 1)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) #define AZTECH_BIT_TUN_CLK (1 << 6)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) #define AZTECH_BIT_TUN_DATA (1 << 7)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) /* bits 0 and 2 are volume control, bits 3..5 are not connected */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) static void aztech_set_pins(void *handle, u8 pins)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) struct radio_isa_card *isa = handle;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) struct aztech *az = container_of(isa, struct aztech, isa);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) u8 bits = az->curvol;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) if (pins & LM7000_DATA)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) bits |= AZTECH_BIT_TUN_DATA;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) if (pins & LM7000_CLK)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) bits |= AZTECH_BIT_TUN_CLK;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) if (pins & LM7000_CE)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) bits |= AZTECH_BIT_TUN_CE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) outb_p(bits, az->isa.io);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) static struct radio_isa_card *aztech_alloc(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) return az ? &az->isa : NULL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) lm7000_set_freq(freq, isa, aztech_set_pins);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) static u32 aztech_g_rxsubchans(struct radio_isa_card *isa)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) if (inb(isa->io) & AZTECH_BIT_MONO)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) return V4L2_TUNER_SUB_MONO;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) return V4L2_TUNER_SUB_STEREO;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) static u32 aztech_g_signal(struct radio_isa_card *isa)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) return (inb(isa->io) & AZTECH_BIT_NOT_TUNED) ? 0 : 0xffff;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) struct aztech *az = container_of(isa, struct aztech, isa);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) if (mute)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) vol = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) az->curvol = (vol & 1) + ((vol & 2) << 1);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) outb(az->curvol, isa->io);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) static const struct radio_isa_ops aztech_ops = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) .alloc = aztech_alloc,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) .s_mute_volume = aztech_s_mute_volume,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) .s_frequency = aztech_s_frequency,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) .g_rxsubchans = aztech_g_rxsubchans,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) .g_signal = aztech_g_signal,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) static const int aztech_ioports[] = { 0x350, 0x358 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) static struct radio_isa_driver aztech_driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) .driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) .match = radio_isa_match,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) .probe = radio_isa_probe,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) .remove = radio_isa_remove,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) .driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) .name = "radio-aztech",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) .io_params = io,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) .radio_nr_params = radio_nr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) .io_ports = aztech_ioports,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) .num_of_io_ports = ARRAY_SIZE(aztech_ioports),
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) .region_size = 8,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) .card = "Aztech Radio",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) .ops = &aztech_ops,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) .has_stereo = true,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) .max_volume = 3,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) static int __init aztech_init(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) return isa_register_driver(&aztech_driver.driver, AZTECH_MAX);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) static void __exit aztech_exit(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) isa_unregister_driver(&aztech_driver.driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) module_init(aztech_init);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) module_exit(aztech_exit);