162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
362306a36Sopenharmony_ci * (c) 1999 R. Offermanns (rolf@offermanns.de)
462306a36Sopenharmony_ci * based on the aimslab radio driver from M. Kirkwood
562306a36Sopenharmony_ci * many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * History:
962306a36Sopenharmony_ci * 1999-05-21	First preview release
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci *  Notes on the hardware:
1262306a36Sopenharmony_ci *  There are two "main" chips on the card:
1362306a36Sopenharmony_ci *  - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
1462306a36Sopenharmony_ci *  - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
1562306a36Sopenharmony_ci *  (you can get the datasheet at the above links)
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci *  Frequency control is done digitally -- ie out(port,encodefreq(95.8));
1862306a36Sopenharmony_ci *  Volume Control is done digitally
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
2162306a36Sopenharmony_ci * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org>
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <linux/module.h>	/* Modules			*/
2562306a36Sopenharmony_ci#include <linux/init.h>		/* Initdata			*/
2662306a36Sopenharmony_ci#include <linux/ioport.h>	/* request_region		*/
2762306a36Sopenharmony_ci#include <linux/videodev2.h>	/* kernel radio structs		*/
2862306a36Sopenharmony_ci#include <linux/mutex.h>
2962306a36Sopenharmony_ci#include <linux/io.h>		/* outb, outb_p			*/
3062306a36Sopenharmony_ci#include <linux/slab.h>
3162306a36Sopenharmony_ci#include <media/v4l2-device.h>
3262306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
3362306a36Sopenharmony_ci#include "radio-isa.h"
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ciMODULE_AUTHOR("R. Offermans & others");
3662306a36Sopenharmony_ciMODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
3762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3862306a36Sopenharmony_ciMODULE_VERSION("0.1.99");
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* Note: there seems to be only one possible port (0x590), but without
4162306a36Sopenharmony_ci   hardware this is hard to verify. For now, this is the only one we will
4262306a36Sopenharmony_ci   support. */
4362306a36Sopenharmony_cistatic int io = 0x590;
4462306a36Sopenharmony_cistatic int radio_nr = -1;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cimodule_param(radio_nr, int, 0444);
4762306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device number");
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define WRT_DIS		0x00
5062306a36Sopenharmony_ci#define CLK_OFF		0x00
5162306a36Sopenharmony_ci#define IIC_DATA	0x01
5262306a36Sopenharmony_ci#define IIC_CLK		0x02
5362306a36Sopenharmony_ci#define DATA		0x04
5462306a36Sopenharmony_ci#define CLK_ON		0x08
5562306a36Sopenharmony_ci#define WRT_EN		0x10
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic struct radio_isa_card *terratec_alloc(void)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	int i;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	if (mute)
6762306a36Sopenharmony_ci		vol = 0;
6862306a36Sopenharmony_ci	vol = vol + (vol * 32); /* change both channels */
6962306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
7062306a36Sopenharmony_ci		if (vol & (0x80 >> i))
7162306a36Sopenharmony_ci			outb(0x80, isa->io + 1);
7262306a36Sopenharmony_ci		else
7362306a36Sopenharmony_ci			outb(0x00, isa->io + 1);
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/* this is the worst part in this driver */
8062306a36Sopenharmony_ci/* many more or less strange things are going on here, but hey, it works :) */
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int terratec_s_frequency(struct radio_isa_card *isa, u32 freq)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	int i;
8562306a36Sopenharmony_ci	int temp;
8662306a36Sopenharmony_ci	long rest;
8762306a36Sopenharmony_ci	unsigned char buffer[25];		/* we have to bit shift 25 registers */
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	freq = freq / 160;			/* convert the freq. to a nice to handle value */
9062306a36Sopenharmony_ci	memset(buffer, 0, sizeof(buffer));
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	rest = freq * 10 + 10700;	/* I once had understood what is going on here */
9362306a36Sopenharmony_ci					/* maybe some wise guy (friedhelm?) can comment this stuff */
9462306a36Sopenharmony_ci	i = 13;
9562306a36Sopenharmony_ci	temp = 102400;
9662306a36Sopenharmony_ci	while (rest != 0) {
9762306a36Sopenharmony_ci		if (rest % temp  == rest)
9862306a36Sopenharmony_ci			buffer[i] = 0;
9962306a36Sopenharmony_ci		else {
10062306a36Sopenharmony_ci			buffer[i] = 1;
10162306a36Sopenharmony_ci			rest = rest - temp;
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci		i--;
10462306a36Sopenharmony_ci		temp = temp / 2;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	for (i = 24; i > -1; i--) {	/* bit shift the values to the radiocard */
10862306a36Sopenharmony_ci		if (buffer[i] == 1) {
10962306a36Sopenharmony_ci			outb(WRT_EN | DATA, isa->io);
11062306a36Sopenharmony_ci			outb(WRT_EN | DATA | CLK_ON, isa->io);
11162306a36Sopenharmony_ci			outb(WRT_EN | DATA, isa->io);
11262306a36Sopenharmony_ci		} else {
11362306a36Sopenharmony_ci			outb(WRT_EN | 0x00, isa->io);
11462306a36Sopenharmony_ci			outb(WRT_EN | 0x00 | CLK_ON, isa->io);
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci	outb(0x00, isa->io);
11862306a36Sopenharmony_ci	return 0;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic u32 terratec_g_signal(struct radio_isa_card *isa)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	/* bit set = no signal present	*/
12462306a36Sopenharmony_ci	return (inb(isa->io) & 2) ? 0 : 0xffff;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic const struct radio_isa_ops terratec_ops = {
12862306a36Sopenharmony_ci	.alloc = terratec_alloc,
12962306a36Sopenharmony_ci	.s_mute_volume = terratec_s_mute_volume,
13062306a36Sopenharmony_ci	.s_frequency = terratec_s_frequency,
13162306a36Sopenharmony_ci	.g_signal = terratec_g_signal,
13262306a36Sopenharmony_ci};
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic const int terratec_ioports[] = { 0x590 };
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic struct radio_isa_driver terratec_driver = {
13762306a36Sopenharmony_ci	.driver = {
13862306a36Sopenharmony_ci		.match		= radio_isa_match,
13962306a36Sopenharmony_ci		.probe		= radio_isa_probe,
14062306a36Sopenharmony_ci		.remove		= radio_isa_remove,
14162306a36Sopenharmony_ci		.driver		= {
14262306a36Sopenharmony_ci			.name	= "radio-terratec",
14362306a36Sopenharmony_ci		},
14462306a36Sopenharmony_ci	},
14562306a36Sopenharmony_ci	.io_params = &io,
14662306a36Sopenharmony_ci	.radio_nr_params = &radio_nr,
14762306a36Sopenharmony_ci	.io_ports = terratec_ioports,
14862306a36Sopenharmony_ci	.num_of_io_ports = ARRAY_SIZE(terratec_ioports),
14962306a36Sopenharmony_ci	.region_size = 2,
15062306a36Sopenharmony_ci	.card = "TerraTec ActiveRadio",
15162306a36Sopenharmony_ci	.ops = &terratec_ops,
15262306a36Sopenharmony_ci	.has_stereo = true,
15362306a36Sopenharmony_ci	.max_volume = 10,
15462306a36Sopenharmony_ci};
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic int __init terratec_init(void)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	return isa_register_driver(&terratec_driver.driver, 1);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic void __exit terratec_exit(void)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	isa_unregister_driver(&terratec_driver.driver);
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cimodule_init(terratec_init);
16762306a36Sopenharmony_cimodule_exit(terratec_exit);
16862306a36Sopenharmony_ci
169