162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* radio-trust.c - Trust FM Radio card driver for Linux 2.2
362306a36Sopenharmony_ci * by Eric Lammerts <eric@scintilla.utwente.nl>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Based on radio-aztech.c. Original notes:
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Adapted to support the Video for Linux API by
862306a36Sopenharmony_ci * Russell Kroll <rkroll@exploits.org>.  Based on original tuner code by:
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Quay Ly
1162306a36Sopenharmony_ci * Donald Song
1262306a36Sopenharmony_ci * Jason Lewis      (jlewis@twilight.vtc.vsc.edu)
1362306a36Sopenharmony_ci * Scott McGrath    (smcgrath@twilight.vtc.vsc.edu)
1462306a36Sopenharmony_ci * William McGrath  (wmcgrath@twilight.vtc.vsc.edu)
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org>
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/init.h>
2162306a36Sopenharmony_ci#include <linux/ioport.h>
2262306a36Sopenharmony_ci#include <linux/videodev2.h>
2362306a36Sopenharmony_ci#include <linux/io.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <media/v4l2-device.h>
2662306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
2762306a36Sopenharmony_ci#include "radio-isa.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciMODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
3062306a36Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Trust FM Radio card.");
3162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3262306a36Sopenharmony_ciMODULE_VERSION("0.1.99");
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#ifndef CONFIG_RADIO_TRUST_PORT
3762306a36Sopenharmony_ci#define CONFIG_RADIO_TRUST_PORT -1
3862306a36Sopenharmony_ci#endif
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define TRUST_MAX 2
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int io[TRUST_MAX] = { [0] = CONFIG_RADIO_TRUST_PORT,
4362306a36Sopenharmony_ci			      [1 ... (TRUST_MAX - 1)] = -1 };
4462306a36Sopenharmony_cistatic int radio_nr[TRUST_MAX] = { [0 ... (TRUST_MAX - 1)] = -1 };
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cimodule_param_array(io, int, NULL, 0444);
4762306a36Sopenharmony_ciMODULE_PARM_DESC(io, "I/O addresses of the Trust FM Radio card (0x350 or 0x358)");
4862306a36Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444);
4962306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device numbers");
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistruct trust {
5262306a36Sopenharmony_ci	struct radio_isa_card isa;
5362306a36Sopenharmony_ci	int ioval;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic struct radio_isa_card *trust_alloc(void)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct trust *tr = kzalloc(sizeof(*tr), GFP_KERNEL);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	return tr ? &tr->isa : NULL;
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/* i2c addresses */
6462306a36Sopenharmony_ci#define TDA7318_ADDR 0x88
6562306a36Sopenharmony_ci#define TSA6060T_ADDR 0xc4
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#define TR_DELAY do { inb(tr->isa.io); inb(tr->isa.io); inb(tr->isa.io); } while (0)
6862306a36Sopenharmony_ci#define TR_SET_SCL outb(tr->ioval |= 2, tr->isa.io)
6962306a36Sopenharmony_ci#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->isa.io)
7062306a36Sopenharmony_ci#define TR_SET_SDA outb(tr->ioval |= 1, tr->isa.io)
7162306a36Sopenharmony_ci#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->isa.io)
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic void write_i2c(struct trust *tr, int n, ...)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	unsigned char val, mask;
7662306a36Sopenharmony_ci	va_list args;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	va_start(args, n);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* start condition */
8162306a36Sopenharmony_ci	TR_SET_SDA;
8262306a36Sopenharmony_ci	TR_SET_SCL;
8362306a36Sopenharmony_ci	TR_DELAY;
8462306a36Sopenharmony_ci	TR_CLR_SDA;
8562306a36Sopenharmony_ci	TR_CLR_SCL;
8662306a36Sopenharmony_ci	TR_DELAY;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	for (; n; n--) {
8962306a36Sopenharmony_ci		val = va_arg(args, unsigned);
9062306a36Sopenharmony_ci		for (mask = 0x80; mask; mask >>= 1) {
9162306a36Sopenharmony_ci			if (val & mask)
9262306a36Sopenharmony_ci				TR_SET_SDA;
9362306a36Sopenharmony_ci			else
9462306a36Sopenharmony_ci				TR_CLR_SDA;
9562306a36Sopenharmony_ci			TR_SET_SCL;
9662306a36Sopenharmony_ci			TR_DELAY;
9762306a36Sopenharmony_ci			TR_CLR_SCL;
9862306a36Sopenharmony_ci			TR_DELAY;
9962306a36Sopenharmony_ci		}
10062306a36Sopenharmony_ci		/* acknowledge bit */
10162306a36Sopenharmony_ci		TR_SET_SDA;
10262306a36Sopenharmony_ci		TR_SET_SCL;
10362306a36Sopenharmony_ci		TR_DELAY;
10462306a36Sopenharmony_ci		TR_CLR_SCL;
10562306a36Sopenharmony_ci		TR_DELAY;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* stop condition */
10962306a36Sopenharmony_ci	TR_CLR_SDA;
11062306a36Sopenharmony_ci	TR_DELAY;
11162306a36Sopenharmony_ci	TR_SET_SCL;
11262306a36Sopenharmony_ci	TR_DELAY;
11362306a36Sopenharmony_ci	TR_SET_SDA;
11462306a36Sopenharmony_ci	TR_DELAY;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	va_end(args);
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int trust_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	struct trust *tr = container_of(isa, struct trust, isa);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	tr->ioval = (tr->ioval & 0xf7) | (mute << 3);
12462306a36Sopenharmony_ci	outb(tr->ioval, isa->io);
12562306a36Sopenharmony_ci	write_i2c(tr, 2, TDA7318_ADDR, vol ^ 0x1f);
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int trust_s_stereo(struct radio_isa_card *isa, bool stereo)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct trust *tr = container_of(isa, struct trust, isa);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	tr->ioval = (tr->ioval & 0xfb) | (!stereo << 2);
13462306a36Sopenharmony_ci	outb(tr->ioval, isa->io);
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic u32 trust_g_signal(struct radio_isa_card *isa)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	int i, v;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	for (i = 0, v = 0; i < 100; i++)
14362306a36Sopenharmony_ci		v |= inb(isa->io);
14462306a36Sopenharmony_ci	return (v & 1) ? 0 : 0xffff;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int trust_s_frequency(struct radio_isa_card *isa, u32 freq)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct trust *tr = container_of(isa, struct trust, isa);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	freq /= 160;	/* Convert to 10 kHz units	*/
15262306a36Sopenharmony_ci	freq += 1070;	/* Add 10.7 MHz IF		*/
15362306a36Sopenharmony_ci	write_i2c(tr, 5, TSA6060T_ADDR, (freq << 1) | 1,
15462306a36Sopenharmony_ci			freq >> 7, 0x60 | ((freq >> 15) & 1), 0);
15562306a36Sopenharmony_ci	return 0;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic int basstreble2chip[15] = {
15962306a36Sopenharmony_ci	0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic int trust_s_ctrl(struct v4l2_ctrl *ctrl)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	struct radio_isa_card *isa =
16562306a36Sopenharmony_ci		container_of(ctrl->handler, struct radio_isa_card, hdl);
16662306a36Sopenharmony_ci	struct trust *tr = container_of(isa, struct trust, isa);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	switch (ctrl->id) {
16962306a36Sopenharmony_ci	case V4L2_CID_AUDIO_BASS:
17062306a36Sopenharmony_ci		write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[ctrl->val]);
17162306a36Sopenharmony_ci		return 0;
17262306a36Sopenharmony_ci	case V4L2_CID_AUDIO_TREBLE:
17362306a36Sopenharmony_ci		write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[ctrl->val]);
17462306a36Sopenharmony_ci		return 0;
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci	return -EINVAL;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops trust_ctrl_ops = {
18062306a36Sopenharmony_ci	.s_ctrl = trust_s_ctrl,
18162306a36Sopenharmony_ci};
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int trust_initialize(struct radio_isa_card *isa)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct trust *tr = container_of(isa, struct trust, isa);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	tr->ioval = 0xf;
18862306a36Sopenharmony_ci	write_i2c(tr, 2, TDA7318_ADDR, 0x80);	/* speaker att. LF = 0 dB */
18962306a36Sopenharmony_ci	write_i2c(tr, 2, TDA7318_ADDR, 0xa0);	/* speaker att. RF = 0 dB */
19062306a36Sopenharmony_ci	write_i2c(tr, 2, TDA7318_ADDR, 0xc0);	/* speaker att. LR = 0 dB */
19162306a36Sopenharmony_ci	write_i2c(tr, 2, TDA7318_ADDR, 0xe0);	/* speaker att. RR = 0 dB */
19262306a36Sopenharmony_ci	write_i2c(tr, 2, TDA7318_ADDR, 0x40);	/* stereo 1 input, gain = 18.75 dB */
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops,
19562306a36Sopenharmony_ci				V4L2_CID_AUDIO_BASS, 0, 15, 1, 8);
19662306a36Sopenharmony_ci	v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops,
19762306a36Sopenharmony_ci				V4L2_CID_AUDIO_TREBLE, 0, 15, 1, 8);
19862306a36Sopenharmony_ci	return isa->hdl.error;
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic const struct radio_isa_ops trust_ops = {
20262306a36Sopenharmony_ci	.init = trust_initialize,
20362306a36Sopenharmony_ci	.alloc = trust_alloc,
20462306a36Sopenharmony_ci	.s_mute_volume = trust_s_mute_volume,
20562306a36Sopenharmony_ci	.s_frequency = trust_s_frequency,
20662306a36Sopenharmony_ci	.s_stereo = trust_s_stereo,
20762306a36Sopenharmony_ci	.g_signal = trust_g_signal,
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic const int trust_ioports[] = { 0x350, 0x358 };
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic struct radio_isa_driver trust_driver = {
21362306a36Sopenharmony_ci	.driver = {
21462306a36Sopenharmony_ci		.match		= radio_isa_match,
21562306a36Sopenharmony_ci		.probe		= radio_isa_probe,
21662306a36Sopenharmony_ci		.remove		= radio_isa_remove,
21762306a36Sopenharmony_ci		.driver		= {
21862306a36Sopenharmony_ci			.name	= "radio-trust",
21962306a36Sopenharmony_ci		},
22062306a36Sopenharmony_ci	},
22162306a36Sopenharmony_ci	.io_params = io,
22262306a36Sopenharmony_ci	.radio_nr_params = radio_nr,
22362306a36Sopenharmony_ci	.io_ports = trust_ioports,
22462306a36Sopenharmony_ci	.num_of_io_ports = ARRAY_SIZE(trust_ioports),
22562306a36Sopenharmony_ci	.region_size = 2,
22662306a36Sopenharmony_ci	.card = "Trust FM Radio",
22762306a36Sopenharmony_ci	.ops = &trust_ops,
22862306a36Sopenharmony_ci	.has_stereo = true,
22962306a36Sopenharmony_ci	.max_volume = 31,
23062306a36Sopenharmony_ci};
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic int __init trust_init(void)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	return isa_register_driver(&trust_driver.driver, TRUST_MAX);
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic void __exit trust_exit(void)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	isa_unregister_driver(&trust_driver.driver);
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cimodule_init(trust_init);
24362306a36Sopenharmony_cimodule_exit(trust_exit);
244