162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* DVB USB compliant Linux driver for the
362306a36Sopenharmony_ci *  - TwinhanDTV Alpha/MagicBoxII USB2.0 DVB-T receiver
462306a36Sopenharmony_ci *  - DigitalNow TinyUSB2 DVB-t receiver
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Thanks to Twinhan who kindly provided hardware and information.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * see Documentation/driver-api/media/drivers/dvb-usb.rst for more information
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci#include "vp7045.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/* debug */
1562306a36Sopenharmony_cistatic int dvb_usb_vp7045_debug;
1662306a36Sopenharmony_cimodule_param_named(debug,dvb_usb_vp7045_debug, int, 0644);
1762306a36Sopenharmony_ciMODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." DVB_USB_DEBUG_STATUS);
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciDVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define deb_info(args...) dprintk(dvb_usb_vp7045_debug,0x01,args)
2262306a36Sopenharmony_ci#define deb_xfer(args...) dprintk(dvb_usb_vp7045_debug,0x02,args)
2362306a36Sopenharmony_ci#define deb_rc(args...)   dprintk(dvb_usb_vp7045_debug,0x04,args)
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ciint vp7045_usb_op(struct dvb_usb_device *d, u8 cmd, u8 *out, int outlen, u8 *in, int inlen, int msec)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	int ret = 0;
2862306a36Sopenharmony_ci	u8 *buf = d->priv;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	buf[0] = cmd;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	if (outlen > 19)
3362306a36Sopenharmony_ci		outlen = 19;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	if (inlen > 11)
3662306a36Sopenharmony_ci		inlen = 11;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	ret = mutex_lock_interruptible(&d->usb_mutex);
3962306a36Sopenharmony_ci	if (ret)
4062306a36Sopenharmony_ci		return ret;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	if (out != NULL && outlen > 0)
4362306a36Sopenharmony_ci		memcpy(&buf[1], out, outlen);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	deb_xfer("out buffer: ");
4662306a36Sopenharmony_ci	debug_dump(buf, outlen+1, deb_xfer);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (usb_control_msg(d->udev,
5062306a36Sopenharmony_ci			usb_sndctrlpipe(d->udev,0),
5162306a36Sopenharmony_ci			TH_COMMAND_OUT, USB_TYPE_VENDOR | USB_DIR_OUT, 0, 0,
5262306a36Sopenharmony_ci			buf, 20, 2000) != 20) {
5362306a36Sopenharmony_ci		err("USB control message 'out' went wrong.");
5462306a36Sopenharmony_ci		ret = -EIO;
5562306a36Sopenharmony_ci		goto unlock;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	msleep(msec);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (usb_control_msg(d->udev,
6162306a36Sopenharmony_ci			usb_rcvctrlpipe(d->udev,0),
6262306a36Sopenharmony_ci			TH_COMMAND_IN, USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
6362306a36Sopenharmony_ci			buf, 12, 2000) != 12) {
6462306a36Sopenharmony_ci		err("USB control message 'in' went wrong.");
6562306a36Sopenharmony_ci		ret = -EIO;
6662306a36Sopenharmony_ci		goto unlock;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	deb_xfer("in buffer: ");
7062306a36Sopenharmony_ci	debug_dump(buf, 12, deb_xfer);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (in != NULL && inlen > 0)
7362306a36Sopenharmony_ci		memcpy(in, &buf[1], inlen);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ciunlock:
7662306a36Sopenharmony_ci	mutex_unlock(&d->usb_mutex);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	return ret;
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ciu8 vp7045_read_reg(struct dvb_usb_device *d, u8 reg)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	u8 obuf[2] = { 0 },v;
8462306a36Sopenharmony_ci	obuf[1] = reg;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	vp7045_usb_op(d,TUNER_REG_READ,obuf,2,&v,1,30);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return v;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int vp7045_power_ctrl(struct dvb_usb_device *d, int onoff)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	u8 v = onoff;
9462306a36Sopenharmony_ci	return vp7045_usb_op(d,SET_TUNER_POWER,&v,1,NULL,0,150);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int vp7045_rc_query(struct dvb_usb_device *d)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	int ret;
10062306a36Sopenharmony_ci	u8 key;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	ret = vp7045_usb_op(d, RC_VAL_READ, NULL, 0, &key, 1, 20);
10362306a36Sopenharmony_ci	if (ret)
10462306a36Sopenharmony_ci		return ret;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	deb_rc("remote query key: %x\n", key);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	if (key != 0x44) {
10962306a36Sopenharmony_ci		/*
11062306a36Sopenharmony_ci		 * The 8 bit address isn't available, but since the remote uses
11162306a36Sopenharmony_ci		 * address 0 we'll use that. nec repeats are ignored too, even
11262306a36Sopenharmony_ci		 * though the remote sends them.
11362306a36Sopenharmony_ci		 */
11462306a36Sopenharmony_ci		rc_keydown(d->rc_dev, RC_PROTO_NEC, RC_SCANCODE_NEC(0, key), 0);
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return 0;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic int vp7045_read_eeprom(struct dvb_usb_device *d,u8 *buf, int len, int offset)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	int i, ret;
12362306a36Sopenharmony_ci	u8 v, br[2];
12462306a36Sopenharmony_ci	for (i=0; i < len; i++) {
12562306a36Sopenharmony_ci		v = offset + i;
12662306a36Sopenharmony_ci		ret = vp7045_usb_op(d, GET_EE_VALUE, &v, 1, br, 2, 5);
12762306a36Sopenharmony_ci		if (ret)
12862306a36Sopenharmony_ci			return ret;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci		buf[i] = br[1];
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci	deb_info("VP7045 EEPROM read (offs: %d, len: %d) : ", offset, i);
13362306a36Sopenharmony_ci	debug_dump(buf, i, deb_info);
13462306a36Sopenharmony_ci	return 0;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int vp7045_read_mac_addr(struct dvb_usb_device *d,u8 mac[6])
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	return vp7045_read_eeprom(d,mac, 6, MAC_0_ADDR);
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int vp7045_frontend_attach(struct dvb_usb_adapter *adap)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	u8 buf[255] = { 0 };
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	vp7045_usb_op(adap->dev,VENDOR_STRING_READ,NULL,0,buf,20,0);
14762306a36Sopenharmony_ci	buf[10] = '\0';
14862306a36Sopenharmony_ci	deb_info("firmware says: %s ",buf);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	vp7045_usb_op(adap->dev,PRODUCT_STRING_READ,NULL,0,buf,20,0);
15162306a36Sopenharmony_ci	buf[10] = '\0';
15262306a36Sopenharmony_ci	deb_info("%s ",buf);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	vp7045_usb_op(adap->dev,FW_VERSION_READ,NULL,0,buf,20,0);
15562306a36Sopenharmony_ci	buf[10] = '\0';
15662306a36Sopenharmony_ci	deb_info("v%s\n",buf);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci/*	Dump the EEPROM */
15962306a36Sopenharmony_ci/*	vp7045_read_eeprom(d,buf, 255, FX2_ID_ADDR); */
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	adap->fe_adap[0].fe = vp7045_fe_attach(adap->dev);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	return 0;
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic struct dvb_usb_device_properties vp7045_properties;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int vp7045_usb_probe(struct usb_interface *intf,
16962306a36Sopenharmony_ci		const struct usb_device_id *id)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	return dvb_usb_device_init(intf, &vp7045_properties,
17262306a36Sopenharmony_ci				   THIS_MODULE, NULL, adapter_nr);
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cienum {
17662306a36Sopenharmony_ci	VISIONPLUS_VP7045_COLD,
17762306a36Sopenharmony_ci	VISIONPLUS_VP7045_WARM,
17862306a36Sopenharmony_ci	VISIONPLUS_TINYUSB2_COLD,
17962306a36Sopenharmony_ci	VISIONPLUS_TINYUSB2_WARM,
18062306a36Sopenharmony_ci};
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic struct usb_device_id vp7045_usb_table[] = {
18362306a36Sopenharmony_ci	DVB_USB_DEV(VISIONPLUS, VISIONPLUS_VP7045_COLD),
18462306a36Sopenharmony_ci	DVB_USB_DEV(VISIONPLUS, VISIONPLUS_VP7045_WARM),
18562306a36Sopenharmony_ci	DVB_USB_DEV(VISIONPLUS, VISIONPLUS_TINYUSB2_COLD),
18662306a36Sopenharmony_ci	DVB_USB_DEV(VISIONPLUS, VISIONPLUS_TINYUSB2_WARM),
18762306a36Sopenharmony_ci	{ }
18862306a36Sopenharmony_ci};
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, vp7045_usb_table);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic struct dvb_usb_device_properties vp7045_properties = {
19362306a36Sopenharmony_ci	.usb_ctrl = CYPRESS_FX2,
19462306a36Sopenharmony_ci	.firmware = "dvb-usb-vp7045-01.fw",
19562306a36Sopenharmony_ci	.size_of_priv = 20,
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	.num_adapters = 1,
19862306a36Sopenharmony_ci	.adapter = {
19962306a36Sopenharmony_ci		{
20062306a36Sopenharmony_ci		.num_frontends = 1,
20162306a36Sopenharmony_ci		.fe = {{
20262306a36Sopenharmony_ci			.frontend_attach  = vp7045_frontend_attach,
20362306a36Sopenharmony_ci			/* parameter for the MPEG2-data transfer */
20462306a36Sopenharmony_ci			.stream = {
20562306a36Sopenharmony_ci				.type = USB_BULK,
20662306a36Sopenharmony_ci				.count = 7,
20762306a36Sopenharmony_ci				.endpoint = 0x02,
20862306a36Sopenharmony_ci				.u = {
20962306a36Sopenharmony_ci					.bulk = {
21062306a36Sopenharmony_ci						.buffersize = 4096,
21162306a36Sopenharmony_ci					}
21262306a36Sopenharmony_ci				}
21362306a36Sopenharmony_ci			},
21462306a36Sopenharmony_ci		}},
21562306a36Sopenharmony_ci		}
21662306a36Sopenharmony_ci	},
21762306a36Sopenharmony_ci	.power_ctrl       = vp7045_power_ctrl,
21862306a36Sopenharmony_ci	.read_mac_address = vp7045_read_mac_addr,
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	.rc.core = {
22162306a36Sopenharmony_ci		.rc_interval	= 400,
22262306a36Sopenharmony_ci		.rc_codes	= RC_MAP_TWINHAN_VP1027_DVBS,
22362306a36Sopenharmony_ci		.module_name    = KBUILD_MODNAME,
22462306a36Sopenharmony_ci		.rc_query	= vp7045_rc_query,
22562306a36Sopenharmony_ci		.allowed_protos = RC_PROTO_BIT_NEC,
22662306a36Sopenharmony_ci		.scancode_mask	= 0xff,
22762306a36Sopenharmony_ci	},
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	.num_device_descs = 2,
23062306a36Sopenharmony_ci	.devices = {
23162306a36Sopenharmony_ci		{ .name = "Twinhan USB2.0 DVB-T receiver (TwinhanDTV Alpha/MagicBox II)",
23262306a36Sopenharmony_ci		  .cold_ids = { &vp7045_usb_table[VISIONPLUS_VP7045_COLD], NULL },
23362306a36Sopenharmony_ci		  .warm_ids = { &vp7045_usb_table[VISIONPLUS_VP7045_WARM], NULL },
23462306a36Sopenharmony_ci		},
23562306a36Sopenharmony_ci		{ .name = "DigitalNow TinyUSB 2 DVB-t Receiver",
23662306a36Sopenharmony_ci		  .cold_ids = { &vp7045_usb_table[VISIONPLUS_TINYUSB2_COLD], NULL },
23762306a36Sopenharmony_ci		  .warm_ids = { &vp7045_usb_table[VISIONPLUS_TINYUSB2_WARM], NULL },
23862306a36Sopenharmony_ci		},
23962306a36Sopenharmony_ci		{ NULL },
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci};
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci/* usb specific object needed to register this driver with the usb subsystem */
24462306a36Sopenharmony_cistatic struct usb_driver vp7045_usb_driver = {
24562306a36Sopenharmony_ci	.name		= "dvb_usb_vp7045",
24662306a36Sopenharmony_ci	.probe		= vp7045_usb_probe,
24762306a36Sopenharmony_ci	.disconnect	= dvb_usb_device_exit,
24862306a36Sopenharmony_ci	.id_table	= vp7045_usb_table,
24962306a36Sopenharmony_ci};
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cimodule_usb_driver(vp7045_usb_driver);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ciMODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
25462306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for Twinhan MagicBox/Alpha and DNTV tinyUSB2 DVB-T USB2.0");
25562306a36Sopenharmony_ciMODULE_VERSION("1.0");
25662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
257