18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * FireDTV driver (formerly known as FireSAT)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2004 Andreas Monitzer <andy@monitzer.com>
68c2ecf20Sopenharmony_ci * Copyright (C) 2008 Ben Backx <ben@bbackx.com>
78c2ecf20Sopenharmony_ci * Copyright (C) 2008 Henrik Kurelid <henrik@kurelid.se>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/bug.h>
118c2ecf20Sopenharmony_ci#include <linux/crc32.h>
128c2ecf20Sopenharmony_ci#include <linux/delay.h>
138c2ecf20Sopenharmony_ci#include <linux/device.h>
148c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
178c2ecf20Sopenharmony_ci#include <linux/mutex.h>
188c2ecf20Sopenharmony_ci#include <linux/string.h>
198c2ecf20Sopenharmony_ci#include <linux/stringify.h>
208c2ecf20Sopenharmony_ci#include <linux/wait.h>
218c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#include "firedtv.h"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define FCP_COMMAND_REGISTER		0xfffff0000b00ULL
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define AVC_CTYPE_CONTROL		0x0
308c2ecf20Sopenharmony_ci#define AVC_CTYPE_STATUS		0x1
318c2ecf20Sopenharmony_ci#define AVC_CTYPE_NOTIFY		0x3
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define AVC_RESPONSE_ACCEPTED		0x9
348c2ecf20Sopenharmony_ci#define AVC_RESPONSE_STABLE		0xc
358c2ecf20Sopenharmony_ci#define AVC_RESPONSE_CHANGED		0xd
368c2ecf20Sopenharmony_ci#define AVC_RESPONSE_INTERIM		0xf
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define AVC_SUBUNIT_TYPE_TUNER		(0x05 << 3)
398c2ecf20Sopenharmony_ci#define AVC_SUBUNIT_TYPE_UNIT		(0x1f << 3)
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define AVC_OPCODE_VENDOR		0x00
428c2ecf20Sopenharmony_ci#define AVC_OPCODE_READ_DESCRIPTOR	0x09
438c2ecf20Sopenharmony_ci#define AVC_OPCODE_DSIT			0xc8
448c2ecf20Sopenharmony_ci#define AVC_OPCODE_DSD			0xcb
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci#define DESCRIPTOR_TUNER_STATUS		0x80
478c2ecf20Sopenharmony_ci#define DESCRIPTOR_SUBUNIT_IDENTIFIER	0x00
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci#define SFE_VENDOR_DE_COMPANYID_0	0x00 /* OUI of Digital Everywhere */
508c2ecf20Sopenharmony_ci#define SFE_VENDOR_DE_COMPANYID_1	0x12
518c2ecf20Sopenharmony_ci#define SFE_VENDOR_DE_COMPANYID_2	0x87
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_REGISTER_REMOTE_CONTROL 0x0a
548c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_LNB_CONTROL		0x52
558c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_TUNE_QPSK		0x58 /* for DVB-S */
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_GET_FIRMWARE_VERSION	0x00
588c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_HOST2CA		0x56
598c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_CA2HOST		0x57
608c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_CISTATUS		0x59
618c2ecf20Sopenharmony_ci#define SFE_VENDOR_OPCODE_TUNE_QPSK2		0x60 /* for DVB-S2 */
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci#define SFE_VENDOR_TAG_CA_RESET			0x00
648c2ecf20Sopenharmony_ci#define SFE_VENDOR_TAG_CA_APPLICATION_INFO	0x01
658c2ecf20Sopenharmony_ci#define SFE_VENDOR_TAG_CA_PMT			0x02
668c2ecf20Sopenharmony_ci#define SFE_VENDOR_TAG_CA_DATE_TIME		0x04
678c2ecf20Sopenharmony_ci#define SFE_VENDOR_TAG_CA_MMI			0x05
688c2ecf20Sopenharmony_ci#define SFE_VENDOR_TAG_CA_ENTER_MENU		0x07
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci#define EN50221_LIST_MANAGEMENT_ONLY	0x03
718c2ecf20Sopenharmony_ci#define EN50221_TAG_APP_INFO		0x9f8021
728c2ecf20Sopenharmony_ci#define EN50221_TAG_CA_INFO		0x9f8031
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistruct avc_command_frame {
758c2ecf20Sopenharmony_ci	u8 ctype;
768c2ecf20Sopenharmony_ci	u8 subunit;
778c2ecf20Sopenharmony_ci	u8 opcode;
788c2ecf20Sopenharmony_ci	u8 operand[509];
798c2ecf20Sopenharmony_ci};
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistruct avc_response_frame {
828c2ecf20Sopenharmony_ci	u8 response;
838c2ecf20Sopenharmony_ci	u8 subunit;
848c2ecf20Sopenharmony_ci	u8 opcode;
858c2ecf20Sopenharmony_ci	u8 operand[509];
868c2ecf20Sopenharmony_ci};
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci#define LAST_OPERAND (509 - 1)
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic inline void clear_operands(struct avc_command_frame *c, int from, int to)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	memset(&c->operand[from], 0, to - from + 1);
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic void pad_operands(struct avc_command_frame *c, int from)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	int to = ALIGN(from, 4);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (from <= to && to <= LAST_OPERAND)
1008c2ecf20Sopenharmony_ci		clear_operands(c, from, to);
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci#define AVC_DEBUG_READ_DESCRIPTOR              0x0001
1048c2ecf20Sopenharmony_ci#define AVC_DEBUG_DSIT                         0x0002
1058c2ecf20Sopenharmony_ci#define AVC_DEBUG_DSD                          0x0004
1068c2ecf20Sopenharmony_ci#define AVC_DEBUG_REGISTER_REMOTE_CONTROL      0x0008
1078c2ecf20Sopenharmony_ci#define AVC_DEBUG_LNB_CONTROL                  0x0010
1088c2ecf20Sopenharmony_ci#define AVC_DEBUG_TUNE_QPSK                    0x0020
1098c2ecf20Sopenharmony_ci#define AVC_DEBUG_TUNE_QPSK2                   0x0040
1108c2ecf20Sopenharmony_ci#define AVC_DEBUG_HOST2CA                      0x0080
1118c2ecf20Sopenharmony_ci#define AVC_DEBUG_CA2HOST                      0x0100
1128c2ecf20Sopenharmony_ci#define AVC_DEBUG_APPLICATION_PMT              0x4000
1138c2ecf20Sopenharmony_ci#define AVC_DEBUG_FCP_PAYLOADS                 0x8000
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic int avc_debug;
1168c2ecf20Sopenharmony_cimodule_param_named(debug, avc_debug, int, 0644);
1178c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Verbose logging (none = 0"
1188c2ecf20Sopenharmony_ci	", FCP subactions"
1198c2ecf20Sopenharmony_ci	": READ DESCRIPTOR = "		__stringify(AVC_DEBUG_READ_DESCRIPTOR)
1208c2ecf20Sopenharmony_ci	", DSIT = "			__stringify(AVC_DEBUG_DSIT)
1218c2ecf20Sopenharmony_ci	", REGISTER_REMOTE_CONTROL = "	__stringify(AVC_DEBUG_REGISTER_REMOTE_CONTROL)
1228c2ecf20Sopenharmony_ci	", LNB CONTROL = "		__stringify(AVC_DEBUG_LNB_CONTROL)
1238c2ecf20Sopenharmony_ci	", TUNE QPSK = "		__stringify(AVC_DEBUG_TUNE_QPSK)
1248c2ecf20Sopenharmony_ci	", TUNE QPSK2 = "		__stringify(AVC_DEBUG_TUNE_QPSK2)
1258c2ecf20Sopenharmony_ci	", HOST2CA = "			__stringify(AVC_DEBUG_HOST2CA)
1268c2ecf20Sopenharmony_ci	", CA2HOST = "			__stringify(AVC_DEBUG_CA2HOST)
1278c2ecf20Sopenharmony_ci	"; Application sent PMT = "	__stringify(AVC_DEBUG_APPLICATION_PMT)
1288c2ecf20Sopenharmony_ci	", FCP payloads = "		__stringify(AVC_DEBUG_FCP_PAYLOADS)
1298c2ecf20Sopenharmony_ci	", or a combination, or all = -1)");
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci/*
1328c2ecf20Sopenharmony_ci * This is a workaround since there is no vendor specific command to retrieve
1338c2ecf20Sopenharmony_ci * ca_info using AVC. If this parameter is not used, ca_system_id will be
1348c2ecf20Sopenharmony_ci * filled with application_manufacturer from ca_app_info.
1358c2ecf20Sopenharmony_ci * Digital Everywhere have said that adding ca_info is on their TODO list.
1368c2ecf20Sopenharmony_ci */
1378c2ecf20Sopenharmony_cistatic unsigned int num_fake_ca_system_ids;
1388c2ecf20Sopenharmony_cistatic int fake_ca_system_ids[4] = { -1, -1, -1, -1 };
1398c2ecf20Sopenharmony_cimodule_param_array(fake_ca_system_ids, int, &num_fake_ca_system_ids, 0644);
1408c2ecf20Sopenharmony_ciMODULE_PARM_DESC(fake_ca_system_ids, "If your CAM application manufacturer "
1418c2ecf20Sopenharmony_ci		 "does not have the same ca_system_id as your CAS, you can "
1428c2ecf20Sopenharmony_ci		 "override what ca_system_ids are presented to the "
1438c2ecf20Sopenharmony_ci		 "application by setting this field to an array of ids.");
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic const char *debug_fcp_ctype(unsigned int ctype)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	static const char *ctypes[] = {
1488c2ecf20Sopenharmony_ci		[0x0] = "CONTROL",		[0x1] = "STATUS",
1498c2ecf20Sopenharmony_ci		[0x2] = "SPECIFIC INQUIRY",	[0x3] = "NOTIFY",
1508c2ecf20Sopenharmony_ci		[0x4] = "GENERAL INQUIRY",	[0x8] = "NOT IMPLEMENTED",
1518c2ecf20Sopenharmony_ci		[0x9] = "ACCEPTED",		[0xa] = "REJECTED",
1528c2ecf20Sopenharmony_ci		[0xb] = "IN TRANSITION",	[0xc] = "IMPLEMENTED/STABLE",
1538c2ecf20Sopenharmony_ci		[0xd] = "CHANGED",		[0xf] = "INTERIM",
1548c2ecf20Sopenharmony_ci	};
1558c2ecf20Sopenharmony_ci	const char *ret = ctype < ARRAY_SIZE(ctypes) ? ctypes[ctype] : NULL;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return ret ? ret : "?";
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic const char *debug_fcp_opcode(unsigned int opcode,
1618c2ecf20Sopenharmony_ci				    const u8 *data, int length)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	switch (opcode) {
1648c2ecf20Sopenharmony_ci	case AVC_OPCODE_VENDOR:
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci	case AVC_OPCODE_READ_DESCRIPTOR:
1678c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_READ_DESCRIPTOR ?
1688c2ecf20Sopenharmony_ci				"ReadDescriptor" : NULL;
1698c2ecf20Sopenharmony_ci	case AVC_OPCODE_DSIT:
1708c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_DSIT ?
1718c2ecf20Sopenharmony_ci				"DirectSelectInfo.Type" : NULL;
1728c2ecf20Sopenharmony_ci	case AVC_OPCODE_DSD:
1738c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_DSD ? "DirectSelectData" : NULL;
1748c2ecf20Sopenharmony_ci	default:
1758c2ecf20Sopenharmony_ci		return "Unknown";
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	if (length < 7 ||
1798c2ecf20Sopenharmony_ci	    data[3] != SFE_VENDOR_DE_COMPANYID_0 ||
1808c2ecf20Sopenharmony_ci	    data[4] != SFE_VENDOR_DE_COMPANYID_1 ||
1818c2ecf20Sopenharmony_ci	    data[5] != SFE_VENDOR_DE_COMPANYID_2)
1828c2ecf20Sopenharmony_ci		return "Vendor/Unknown";
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	switch (data[6]) {
1858c2ecf20Sopenharmony_ci	case SFE_VENDOR_OPCODE_REGISTER_REMOTE_CONTROL:
1868c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_REGISTER_REMOTE_CONTROL ?
1878c2ecf20Sopenharmony_ci				"RegisterRC" : NULL;
1888c2ecf20Sopenharmony_ci	case SFE_VENDOR_OPCODE_LNB_CONTROL:
1898c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_LNB_CONTROL ? "LNBControl" : NULL;
1908c2ecf20Sopenharmony_ci	case SFE_VENDOR_OPCODE_TUNE_QPSK:
1918c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_TUNE_QPSK ? "TuneQPSK" : NULL;
1928c2ecf20Sopenharmony_ci	case SFE_VENDOR_OPCODE_TUNE_QPSK2:
1938c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_TUNE_QPSK2 ? "TuneQPSK2" : NULL;
1948c2ecf20Sopenharmony_ci	case SFE_VENDOR_OPCODE_HOST2CA:
1958c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_HOST2CA ? "Host2CA" : NULL;
1968c2ecf20Sopenharmony_ci	case SFE_VENDOR_OPCODE_CA2HOST:
1978c2ecf20Sopenharmony_ci		return avc_debug & AVC_DEBUG_CA2HOST ? "CA2Host" : NULL;
1988c2ecf20Sopenharmony_ci	}
1998c2ecf20Sopenharmony_ci	return "Vendor/Unknown";
2008c2ecf20Sopenharmony_ci}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic void debug_fcp(const u8 *data, int length)
2038c2ecf20Sopenharmony_ci{
2048c2ecf20Sopenharmony_ci	unsigned int subunit_type, subunit_id, opcode;
2058c2ecf20Sopenharmony_ci	const char *op, *prefix;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	prefix       = data[0] > 7 ? "FCP <- " : "FCP -> ";
2088c2ecf20Sopenharmony_ci	subunit_type = data[1] >> 3;
2098c2ecf20Sopenharmony_ci	subunit_id   = data[1] & 7;
2108c2ecf20Sopenharmony_ci	opcode       = subunit_type == 0x1e || subunit_id == 5 ? ~0 : data[2];
2118c2ecf20Sopenharmony_ci	op           = debug_fcp_opcode(opcode, data, length);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	if (op) {
2148c2ecf20Sopenharmony_ci		printk(KERN_INFO "%ssu=%x.%x l=%d: %-8s - %s\n",
2158c2ecf20Sopenharmony_ci		       prefix, subunit_type, subunit_id, length,
2168c2ecf20Sopenharmony_ci		       debug_fcp_ctype(data[0]), op);
2178c2ecf20Sopenharmony_ci		if (avc_debug & AVC_DEBUG_FCP_PAYLOADS)
2188c2ecf20Sopenharmony_ci			print_hex_dump(KERN_INFO, prefix, DUMP_PREFIX_NONE,
2198c2ecf20Sopenharmony_ci				       16, 1, data, length, false);
2208c2ecf20Sopenharmony_ci	}
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cistatic void debug_pmt(char *msg, int length)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	printk(KERN_INFO "APP PMT -> l=%d\n", length);
2268c2ecf20Sopenharmony_ci	print_hex_dump(KERN_INFO, "APP PMT -> ", DUMP_PREFIX_NONE,
2278c2ecf20Sopenharmony_ci		       16, 1, msg, length, false);
2288c2ecf20Sopenharmony_ci}
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_cistatic int avc_write(struct firedtv *fdtv)
2318c2ecf20Sopenharmony_ci{
2328c2ecf20Sopenharmony_ci	int err, retry;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	fdtv->avc_reply_received = false;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	for (retry = 0; retry < 6; retry++) {
2378c2ecf20Sopenharmony_ci		if (unlikely(avc_debug))
2388c2ecf20Sopenharmony_ci			debug_fcp(fdtv->avc_data, fdtv->avc_data_length);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci		err = fdtv_write(fdtv, FCP_COMMAND_REGISTER,
2418c2ecf20Sopenharmony_ci				 fdtv->avc_data, fdtv->avc_data_length);
2428c2ecf20Sopenharmony_ci		if (err) {
2438c2ecf20Sopenharmony_ci			dev_err(fdtv->device, "FCP command write failed\n");
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci			return err;
2468c2ecf20Sopenharmony_ci		}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci		/*
2498c2ecf20Sopenharmony_ci		 * AV/C specs say that answers should be sent within 150 ms.
2508c2ecf20Sopenharmony_ci		 * Time out after 200 ms.
2518c2ecf20Sopenharmony_ci		 */
2528c2ecf20Sopenharmony_ci		if (wait_event_timeout(fdtv->avc_wait,
2538c2ecf20Sopenharmony_ci				       fdtv->avc_reply_received,
2548c2ecf20Sopenharmony_ci				       msecs_to_jiffies(200)) != 0)
2558c2ecf20Sopenharmony_ci			return 0;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci	dev_err(fdtv->device, "FCP response timed out\n");
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	return -ETIMEDOUT;
2608c2ecf20Sopenharmony_ci}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_cistatic bool is_register_rc(struct avc_response_frame *r)
2638c2ecf20Sopenharmony_ci{
2648c2ecf20Sopenharmony_ci	return r->opcode     == AVC_OPCODE_VENDOR &&
2658c2ecf20Sopenharmony_ci	       r->operand[0] == SFE_VENDOR_DE_COMPANYID_0 &&
2668c2ecf20Sopenharmony_ci	       r->operand[1] == SFE_VENDOR_DE_COMPANYID_1 &&
2678c2ecf20Sopenharmony_ci	       r->operand[2] == SFE_VENDOR_DE_COMPANYID_2 &&
2688c2ecf20Sopenharmony_ci	       r->operand[3] == SFE_VENDOR_OPCODE_REGISTER_REMOTE_CONTROL;
2698c2ecf20Sopenharmony_ci}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ciint avc_recv(struct firedtv *fdtv, void *data, size_t length)
2728c2ecf20Sopenharmony_ci{
2738c2ecf20Sopenharmony_ci	struct avc_response_frame *r = data;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	if (unlikely(avc_debug))
2768c2ecf20Sopenharmony_ci		debug_fcp(data, length);
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	if (length >= 8 && is_register_rc(r)) {
2798c2ecf20Sopenharmony_ci		switch (r->response) {
2808c2ecf20Sopenharmony_ci		case AVC_RESPONSE_CHANGED:
2818c2ecf20Sopenharmony_ci			fdtv_handle_rc(fdtv, r->operand[4] << 8 | r->operand[5]);
2828c2ecf20Sopenharmony_ci			schedule_work(&fdtv->remote_ctrl_work);
2838c2ecf20Sopenharmony_ci			break;
2848c2ecf20Sopenharmony_ci		case AVC_RESPONSE_INTERIM:
2858c2ecf20Sopenharmony_ci			if (is_register_rc((void *)fdtv->avc_data))
2868c2ecf20Sopenharmony_ci				goto wake;
2878c2ecf20Sopenharmony_ci			break;
2888c2ecf20Sopenharmony_ci		default:
2898c2ecf20Sopenharmony_ci			dev_info(fdtv->device,
2908c2ecf20Sopenharmony_ci				 "remote control result = %d\n", r->response);
2918c2ecf20Sopenharmony_ci		}
2928c2ecf20Sopenharmony_ci		return 0;
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	if (fdtv->avc_reply_received) {
2968c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "out-of-order AVC response, ignored\n");
2978c2ecf20Sopenharmony_ci		return -EIO;
2988c2ecf20Sopenharmony_ci	}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	memcpy(fdtv->avc_data, data, length);
3018c2ecf20Sopenharmony_ci	fdtv->avc_data_length = length;
3028c2ecf20Sopenharmony_ciwake:
3038c2ecf20Sopenharmony_ci	fdtv->avc_reply_received = true;
3048c2ecf20Sopenharmony_ci	wake_up(&fdtv->avc_wait);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	return 0;
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic int add_pid_filter(struct firedtv *fdtv, u8 *operand)
3108c2ecf20Sopenharmony_ci{
3118c2ecf20Sopenharmony_ci	int i, n, pos = 1;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	for (i = 0, n = 0; i < 16; i++) {
3148c2ecf20Sopenharmony_ci		if (test_bit(i, &fdtv->channel_active)) {
3158c2ecf20Sopenharmony_ci			operand[pos++] = 0x13; /* flowfunction relay */
3168c2ecf20Sopenharmony_ci			operand[pos++] = 0x80; /* dsd_sel_spec_valid_flags -> PID */
3178c2ecf20Sopenharmony_ci			operand[pos++] = (fdtv->channel_pid[i] >> 8) & 0x1f;
3188c2ecf20Sopenharmony_ci			operand[pos++] = fdtv->channel_pid[i] & 0xff;
3198c2ecf20Sopenharmony_ci			operand[pos++] = 0x00; /* tableID */
3208c2ecf20Sopenharmony_ci			operand[pos++] = 0x00; /* filter_length */
3218c2ecf20Sopenharmony_ci			n++;
3228c2ecf20Sopenharmony_ci		}
3238c2ecf20Sopenharmony_ci	}
3248c2ecf20Sopenharmony_ci	operand[0] = n;
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	return pos;
3278c2ecf20Sopenharmony_ci}
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci/*
3308c2ecf20Sopenharmony_ci * tuning command for setting the relative LNB frequency
3318c2ecf20Sopenharmony_ci * (not supported by the AVC standard)
3328c2ecf20Sopenharmony_ci */
3338c2ecf20Sopenharmony_cistatic int avc_tuner_tuneqpsk(struct firedtv *fdtv,
3348c2ecf20Sopenharmony_ci			      struct dtv_frontend_properties *p)
3358c2ecf20Sopenharmony_ci{
3368c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	c->opcode = AVC_OPCODE_VENDOR;
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
3418c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
3428c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
3438c2ecf20Sopenharmony_ci	if (fdtv->type == FIREDTV_DVB_S2)
3448c2ecf20Sopenharmony_ci		c->operand[3] = SFE_VENDOR_OPCODE_TUNE_QPSK2;
3458c2ecf20Sopenharmony_ci	else
3468c2ecf20Sopenharmony_ci		c->operand[3] = SFE_VENDOR_OPCODE_TUNE_QPSK;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	c->operand[4] = (p->frequency >> 24) & 0xff;
3498c2ecf20Sopenharmony_ci	c->operand[5] = (p->frequency >> 16) & 0xff;
3508c2ecf20Sopenharmony_ci	c->operand[6] = (p->frequency >> 8) & 0xff;
3518c2ecf20Sopenharmony_ci	c->operand[7] = p->frequency & 0xff;
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	c->operand[8] = ((p->symbol_rate / 1000) >> 8) & 0xff;
3548c2ecf20Sopenharmony_ci	c->operand[9] = (p->symbol_rate / 1000) & 0xff;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	switch (p->fec_inner) {
3578c2ecf20Sopenharmony_ci	case FEC_1_2:	c->operand[10] = 0x1; break;
3588c2ecf20Sopenharmony_ci	case FEC_2_3:	c->operand[10] = 0x2; break;
3598c2ecf20Sopenharmony_ci	case FEC_3_4:	c->operand[10] = 0x3; break;
3608c2ecf20Sopenharmony_ci	case FEC_5_6:	c->operand[10] = 0x4; break;
3618c2ecf20Sopenharmony_ci	case FEC_7_8:	c->operand[10] = 0x5; break;
3628c2ecf20Sopenharmony_ci	case FEC_4_5:
3638c2ecf20Sopenharmony_ci	case FEC_8_9:
3648c2ecf20Sopenharmony_ci	case FEC_AUTO:
3658c2ecf20Sopenharmony_ci	default:	c->operand[10] = 0x0;
3668c2ecf20Sopenharmony_ci	}
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	if (fdtv->voltage == 0xff)
3698c2ecf20Sopenharmony_ci		c->operand[11] = 0xff;
3708c2ecf20Sopenharmony_ci	else if (fdtv->voltage == SEC_VOLTAGE_18) /* polarisation */
3718c2ecf20Sopenharmony_ci		c->operand[11] = 0;
3728c2ecf20Sopenharmony_ci	else
3738c2ecf20Sopenharmony_ci		c->operand[11] = 1;
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	if (fdtv->tone == 0xff)
3768c2ecf20Sopenharmony_ci		c->operand[12] = 0xff;
3778c2ecf20Sopenharmony_ci	else if (fdtv->tone == SEC_TONE_ON) /* band */
3788c2ecf20Sopenharmony_ci		c->operand[12] = 1;
3798c2ecf20Sopenharmony_ci	else
3808c2ecf20Sopenharmony_ci		c->operand[12] = 0;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	if (fdtv->type == FIREDTV_DVB_S2) {
3838c2ecf20Sopenharmony_ci		if (fdtv->fe.dtv_property_cache.delivery_system == SYS_DVBS2) {
3848c2ecf20Sopenharmony_ci			switch (fdtv->fe.dtv_property_cache.modulation) {
3858c2ecf20Sopenharmony_ci			case QAM_16:		c->operand[13] = 0x1; break;
3868c2ecf20Sopenharmony_ci			case QPSK:		c->operand[13] = 0x2; break;
3878c2ecf20Sopenharmony_ci			case PSK_8:		c->operand[13] = 0x3; break;
3888c2ecf20Sopenharmony_ci			default:		c->operand[13] = 0x2; break;
3898c2ecf20Sopenharmony_ci			}
3908c2ecf20Sopenharmony_ci			switch (fdtv->fe.dtv_property_cache.rolloff) {
3918c2ecf20Sopenharmony_ci			case ROLLOFF_35:	c->operand[14] = 0x2; break;
3928c2ecf20Sopenharmony_ci			case ROLLOFF_20:	c->operand[14] = 0x0; break;
3938c2ecf20Sopenharmony_ci			case ROLLOFF_25:	c->operand[14] = 0x1; break;
3948c2ecf20Sopenharmony_ci			case ROLLOFF_AUTO:
3958c2ecf20Sopenharmony_ci			default:		c->operand[14] = 0x2; break;
3968c2ecf20Sopenharmony_ci			/* case ROLLOFF_NONE:	c->operand[14] = 0xff; break; */
3978c2ecf20Sopenharmony_ci			}
3988c2ecf20Sopenharmony_ci			switch (fdtv->fe.dtv_property_cache.pilot) {
3998c2ecf20Sopenharmony_ci			case PILOT_AUTO:	c->operand[15] = 0x0; break;
4008c2ecf20Sopenharmony_ci			case PILOT_OFF:		c->operand[15] = 0x0; break;
4018c2ecf20Sopenharmony_ci			case PILOT_ON:		c->operand[15] = 0x1; break;
4028c2ecf20Sopenharmony_ci			}
4038c2ecf20Sopenharmony_ci		} else {
4048c2ecf20Sopenharmony_ci			c->operand[13] = 0x1;  /* auto modulation */
4058c2ecf20Sopenharmony_ci			c->operand[14] = 0xff; /* disable rolloff */
4068c2ecf20Sopenharmony_ci			c->operand[15] = 0xff; /* disable pilot */
4078c2ecf20Sopenharmony_ci		}
4088c2ecf20Sopenharmony_ci		return 16;
4098c2ecf20Sopenharmony_ci	} else {
4108c2ecf20Sopenharmony_ci		return 13;
4118c2ecf20Sopenharmony_ci	}
4128c2ecf20Sopenharmony_ci}
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_cistatic int avc_tuner_dsd_dvb_c(struct firedtv *fdtv,
4158c2ecf20Sopenharmony_ci			       struct dtv_frontend_properties *p)
4168c2ecf20Sopenharmony_ci{
4178c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	c->opcode = AVC_OPCODE_DSD;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	c->operand[0] = 0;    /* source plug */
4228c2ecf20Sopenharmony_ci	c->operand[1] = 0xd2; /* subfunction replace */
4238c2ecf20Sopenharmony_ci	c->operand[2] = 0x20; /* system id = DVB */
4248c2ecf20Sopenharmony_ci	c->operand[3] = 0x00; /* antenna number */
4258c2ecf20Sopenharmony_ci	c->operand[4] = 0x11; /* system_specific_multiplex selection_length */
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	/* multiplex_valid_flags, high byte */
4288c2ecf20Sopenharmony_ci	c->operand[5] =   0 << 7 /* reserved */
4298c2ecf20Sopenharmony_ci			| 0 << 6 /* Polarisation */
4308c2ecf20Sopenharmony_ci			| 0 << 5 /* Orbital_Pos */
4318c2ecf20Sopenharmony_ci			| 1 << 4 /* Frequency */
4328c2ecf20Sopenharmony_ci			| 1 << 3 /* Symbol_Rate */
4338c2ecf20Sopenharmony_ci			| 0 << 2 /* FEC_outer */
4348c2ecf20Sopenharmony_ci			| (p->fec_inner  != FEC_AUTO ? 1 << 1 : 0)
4358c2ecf20Sopenharmony_ci			| (p->modulation != QAM_AUTO ? 1 << 0 : 0);
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	/* multiplex_valid_flags, low byte */
4388c2ecf20Sopenharmony_ci	c->operand[6] =   0 << 7 /* NetworkID */
4398c2ecf20Sopenharmony_ci			| 0 << 0 /* reserved */ ;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	c->operand[7]  = 0x00;
4428c2ecf20Sopenharmony_ci	c->operand[8]  = 0x00;
4438c2ecf20Sopenharmony_ci	c->operand[9]  = 0x00;
4448c2ecf20Sopenharmony_ci	c->operand[10] = 0x00;
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	c->operand[11] = (((p->frequency / 4000) >> 16) & 0xff) | (2 << 6);
4478c2ecf20Sopenharmony_ci	c->operand[12] = ((p->frequency / 4000) >> 8) & 0xff;
4488c2ecf20Sopenharmony_ci	c->operand[13] = (p->frequency / 4000) & 0xff;
4498c2ecf20Sopenharmony_ci	c->operand[14] = ((p->symbol_rate / 1000) >> 12) & 0xff;
4508c2ecf20Sopenharmony_ci	c->operand[15] = ((p->symbol_rate / 1000) >> 4) & 0xff;
4518c2ecf20Sopenharmony_ci	c->operand[16] = ((p->symbol_rate / 1000) << 4) & 0xf0;
4528c2ecf20Sopenharmony_ci	c->operand[17] = 0x00;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	switch (p->fec_inner) {
4558c2ecf20Sopenharmony_ci	case FEC_1_2:	c->operand[18] = 0x1; break;
4568c2ecf20Sopenharmony_ci	case FEC_2_3:	c->operand[18] = 0x2; break;
4578c2ecf20Sopenharmony_ci	case FEC_3_4:	c->operand[18] = 0x3; break;
4588c2ecf20Sopenharmony_ci	case FEC_5_6:	c->operand[18] = 0x4; break;
4598c2ecf20Sopenharmony_ci	case FEC_7_8:	c->operand[18] = 0x5; break;
4608c2ecf20Sopenharmony_ci	case FEC_8_9:	c->operand[18] = 0x6; break;
4618c2ecf20Sopenharmony_ci	case FEC_4_5:	c->operand[18] = 0x8; break;
4628c2ecf20Sopenharmony_ci	case FEC_AUTO:
4638c2ecf20Sopenharmony_ci	default:	c->operand[18] = 0x0;
4648c2ecf20Sopenharmony_ci	}
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	switch (p->modulation) {
4678c2ecf20Sopenharmony_ci	case QAM_16:	c->operand[19] = 0x08; break;
4688c2ecf20Sopenharmony_ci	case QAM_32:	c->operand[19] = 0x10; break;
4698c2ecf20Sopenharmony_ci	case QAM_64:	c->operand[19] = 0x18; break;
4708c2ecf20Sopenharmony_ci	case QAM_128:	c->operand[19] = 0x20; break;
4718c2ecf20Sopenharmony_ci	case QAM_256:	c->operand[19] = 0x28; break;
4728c2ecf20Sopenharmony_ci	case QAM_AUTO:
4738c2ecf20Sopenharmony_ci	default:	c->operand[19] = 0x00;
4748c2ecf20Sopenharmony_ci	}
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	c->operand[20] = 0x00;
4778c2ecf20Sopenharmony_ci	c->operand[21] = 0x00;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	return 22 + add_pid_filter(fdtv, &c->operand[22]);
4808c2ecf20Sopenharmony_ci}
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_cistatic int avc_tuner_dsd_dvb_t(struct firedtv *fdtv,
4838c2ecf20Sopenharmony_ci			       struct dtv_frontend_properties *p)
4848c2ecf20Sopenharmony_ci{
4858c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci	c->opcode = AVC_OPCODE_DSD;
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	c->operand[0] = 0;    /* source plug */
4908c2ecf20Sopenharmony_ci	c->operand[1] = 0xd2; /* subfunction replace */
4918c2ecf20Sopenharmony_ci	c->operand[2] = 0x20; /* system id = DVB */
4928c2ecf20Sopenharmony_ci	c->operand[3] = 0x00; /* antenna number */
4938c2ecf20Sopenharmony_ci	c->operand[4] = 0x0c; /* system_specific_multiplex selection_length */
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	/* multiplex_valid_flags, high byte */
4968c2ecf20Sopenharmony_ci	c->operand[5] =
4978c2ecf20Sopenharmony_ci	      0 << 7 /* reserved */
4988c2ecf20Sopenharmony_ci	    | 1 << 6 /* CenterFrequency */
4998c2ecf20Sopenharmony_ci	    | (p->bandwidth_hz != 0        ? 1 << 5 : 0)
5008c2ecf20Sopenharmony_ci	    | (p->modulation  != QAM_AUTO              ? 1 << 4 : 0)
5018c2ecf20Sopenharmony_ci	    | (p->hierarchy != HIERARCHY_AUTO ? 1 << 3 : 0)
5028c2ecf20Sopenharmony_ci	    | (p->code_rate_HP   != FEC_AUTO              ? 1 << 2 : 0)
5038c2ecf20Sopenharmony_ci	    | (p->code_rate_LP   != FEC_AUTO              ? 1 << 1 : 0)
5048c2ecf20Sopenharmony_ci	    | (p->guard_interval != GUARD_INTERVAL_AUTO   ? 1 << 0 : 0);
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	/* multiplex_valid_flags, low byte */
5078c2ecf20Sopenharmony_ci	c->operand[6] =
5088c2ecf20Sopenharmony_ci	      0 << 7 /* NetworkID */
5098c2ecf20Sopenharmony_ci	    | (p->transmission_mode != TRANSMISSION_MODE_AUTO ? 1 << 6 : 0)
5108c2ecf20Sopenharmony_ci	    | 0 << 5 /* OtherFrequencyFlag */
5118c2ecf20Sopenharmony_ci	    | 0 << 0 /* reserved */ ;
5128c2ecf20Sopenharmony_ci
5138c2ecf20Sopenharmony_ci	c->operand[7]  = 0x0;
5148c2ecf20Sopenharmony_ci	c->operand[8]  = (p->frequency / 10) >> 24;
5158c2ecf20Sopenharmony_ci	c->operand[9]  = ((p->frequency / 10) >> 16) & 0xff;
5168c2ecf20Sopenharmony_ci	c->operand[10] = ((p->frequency / 10) >>  8) & 0xff;
5178c2ecf20Sopenharmony_ci	c->operand[11] = (p->frequency / 10) & 0xff;
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	switch (p->bandwidth_hz) {
5208c2ecf20Sopenharmony_ci	case 7000000:	c->operand[12] = 0x20; break;
5218c2ecf20Sopenharmony_ci	case 8000000:
5228c2ecf20Sopenharmony_ci	case 6000000:	/* not defined by AVC spec */
5238c2ecf20Sopenharmony_ci	case 0:
5248c2ecf20Sopenharmony_ci	default:		c->operand[12] = 0x00;
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	switch (p->modulation) {
5288c2ecf20Sopenharmony_ci	case QAM_16:	c->operand[13] = 1 << 6; break;
5298c2ecf20Sopenharmony_ci	case QAM_64:	c->operand[13] = 2 << 6; break;
5308c2ecf20Sopenharmony_ci	case QPSK:
5318c2ecf20Sopenharmony_ci	default:	c->operand[13] = 0x00;
5328c2ecf20Sopenharmony_ci	}
5338c2ecf20Sopenharmony_ci
5348c2ecf20Sopenharmony_ci	switch (p->hierarchy) {
5358c2ecf20Sopenharmony_ci	case HIERARCHY_1:	c->operand[13] |= 1 << 3; break;
5368c2ecf20Sopenharmony_ci	case HIERARCHY_2:	c->operand[13] |= 2 << 3; break;
5378c2ecf20Sopenharmony_ci	case HIERARCHY_4:	c->operand[13] |= 3 << 3; break;
5388c2ecf20Sopenharmony_ci	case HIERARCHY_AUTO:
5398c2ecf20Sopenharmony_ci	case HIERARCHY_NONE:
5408c2ecf20Sopenharmony_ci	default:		break;
5418c2ecf20Sopenharmony_ci	}
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci	switch (p->code_rate_HP) {
5448c2ecf20Sopenharmony_ci	case FEC_2_3:	c->operand[13] |= 1; break;
5458c2ecf20Sopenharmony_ci	case FEC_3_4:	c->operand[13] |= 2; break;
5468c2ecf20Sopenharmony_ci	case FEC_5_6:	c->operand[13] |= 3; break;
5478c2ecf20Sopenharmony_ci	case FEC_7_8:	c->operand[13] |= 4; break;
5488c2ecf20Sopenharmony_ci	case FEC_1_2:
5498c2ecf20Sopenharmony_ci	default:	break;
5508c2ecf20Sopenharmony_ci	}
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	switch (p->code_rate_LP) {
5538c2ecf20Sopenharmony_ci	case FEC_2_3:	c->operand[14] = 1 << 5; break;
5548c2ecf20Sopenharmony_ci	case FEC_3_4:	c->operand[14] = 2 << 5; break;
5558c2ecf20Sopenharmony_ci	case FEC_5_6:	c->operand[14] = 3 << 5; break;
5568c2ecf20Sopenharmony_ci	case FEC_7_8:	c->operand[14] = 4 << 5; break;
5578c2ecf20Sopenharmony_ci	case FEC_1_2:
5588c2ecf20Sopenharmony_ci	default:	c->operand[14] = 0x00; break;
5598c2ecf20Sopenharmony_ci	}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	switch (p->guard_interval) {
5628c2ecf20Sopenharmony_ci	case GUARD_INTERVAL_1_16:	c->operand[14] |= 1 << 3; break;
5638c2ecf20Sopenharmony_ci	case GUARD_INTERVAL_1_8:	c->operand[14] |= 2 << 3; break;
5648c2ecf20Sopenharmony_ci	case GUARD_INTERVAL_1_4:	c->operand[14] |= 3 << 3; break;
5658c2ecf20Sopenharmony_ci	case GUARD_INTERVAL_1_32:
5668c2ecf20Sopenharmony_ci	case GUARD_INTERVAL_AUTO:
5678c2ecf20Sopenharmony_ci	default:			break;
5688c2ecf20Sopenharmony_ci	}
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci	switch (p->transmission_mode) {
5718c2ecf20Sopenharmony_ci	case TRANSMISSION_MODE_8K:	c->operand[14] |= 1 << 1; break;
5728c2ecf20Sopenharmony_ci	case TRANSMISSION_MODE_2K:
5738c2ecf20Sopenharmony_ci	case TRANSMISSION_MODE_AUTO:
5748c2ecf20Sopenharmony_ci	default:			break;
5758c2ecf20Sopenharmony_ci	}
5768c2ecf20Sopenharmony_ci
5778c2ecf20Sopenharmony_ci	c->operand[15] = 0x00; /* network_ID[0] */
5788c2ecf20Sopenharmony_ci	c->operand[16] = 0x00; /* network_ID[1] */
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci	return 17 + add_pid_filter(fdtv, &c->operand[17]);
5818c2ecf20Sopenharmony_ci}
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ciint avc_tuner_dsd(struct firedtv *fdtv,
5848c2ecf20Sopenharmony_ci		  struct dtv_frontend_properties *p)
5858c2ecf20Sopenharmony_ci{
5868c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
5878c2ecf20Sopenharmony_ci	int pos, ret;
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
5928c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	switch (fdtv->type) {
5958c2ecf20Sopenharmony_ci	case FIREDTV_DVB_S:
5968c2ecf20Sopenharmony_ci	case FIREDTV_DVB_S2: pos = avc_tuner_tuneqpsk(fdtv, p); break;
5978c2ecf20Sopenharmony_ci	case FIREDTV_DVB_C: pos = avc_tuner_dsd_dvb_c(fdtv, p); break;
5988c2ecf20Sopenharmony_ci	case FIREDTV_DVB_T: pos = avc_tuner_dsd_dvb_t(fdtv, p); break;
5998c2ecf20Sopenharmony_ci	default:
6008c2ecf20Sopenharmony_ci		BUG();
6018c2ecf20Sopenharmony_ci	}
6028c2ecf20Sopenharmony_ci	pad_operands(c, pos);
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_ci	fdtv->avc_data_length = ALIGN(3 + pos, 4);
6058c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
6068c2ecf20Sopenharmony_ci#if 0
6078c2ecf20Sopenharmony_ci	/*
6088c2ecf20Sopenharmony_ci	 * FIXME:
6098c2ecf20Sopenharmony_ci	 * u8 *status was an out-parameter of avc_tuner_dsd, unused by caller.
6108c2ecf20Sopenharmony_ci	 * Check for AVC_RESPONSE_ACCEPTED here instead?
6118c2ecf20Sopenharmony_ci	 */
6128c2ecf20Sopenharmony_ci	if (status)
6138c2ecf20Sopenharmony_ci		*status = r->operand[2];
6148c2ecf20Sopenharmony_ci#endif
6158c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_ci	if (ret == 0)
6188c2ecf20Sopenharmony_ci		msleep(500);
6198c2ecf20Sopenharmony_ci
6208c2ecf20Sopenharmony_ci	return ret;
6218c2ecf20Sopenharmony_ci}
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ciint avc_tuner_set_pids(struct firedtv *fdtv, unsigned char pidc, u16 pid[])
6248c2ecf20Sopenharmony_ci{
6258c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
6268c2ecf20Sopenharmony_ci	int ret, pos, k;
6278c2ecf20Sopenharmony_ci
6288c2ecf20Sopenharmony_ci	if (pidc > 16 && pidc != 0xff)
6298c2ecf20Sopenharmony_ci		return -EINVAL;
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
6348c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
6358c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_DSD;
6368c2ecf20Sopenharmony_ci
6378c2ecf20Sopenharmony_ci	c->operand[0] = 0;	/* source plug */
6388c2ecf20Sopenharmony_ci	c->operand[1] = 0xd2;	/* subfunction replace */
6398c2ecf20Sopenharmony_ci	c->operand[2] = 0x20;	/* system id = DVB */
6408c2ecf20Sopenharmony_ci	c->operand[3] = 0x00;	/* antenna number */
6418c2ecf20Sopenharmony_ci	c->operand[4] = 0x00;	/* system_specific_multiplex selection_length */
6428c2ecf20Sopenharmony_ci	c->operand[5] = pidc;	/* Nr_of_dsd_sel_specs */
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_ci	pos = 6;
6458c2ecf20Sopenharmony_ci	if (pidc != 0xff)
6468c2ecf20Sopenharmony_ci		for (k = 0; k < pidc; k++) {
6478c2ecf20Sopenharmony_ci			c->operand[pos++] = 0x13; /* flowfunction relay */
6488c2ecf20Sopenharmony_ci			c->operand[pos++] = 0x80; /* dsd_sel_spec_valid_flags -> PID */
6498c2ecf20Sopenharmony_ci			c->operand[pos++] = (pid[k] >> 8) & 0x1f;
6508c2ecf20Sopenharmony_ci			c->operand[pos++] = pid[k] & 0xff;
6518c2ecf20Sopenharmony_ci			c->operand[pos++] = 0x00; /* tableID */
6528c2ecf20Sopenharmony_ci			c->operand[pos++] = 0x00; /* filter_length */
6538c2ecf20Sopenharmony_ci		}
6548c2ecf20Sopenharmony_ci	pad_operands(c, pos);
6558c2ecf20Sopenharmony_ci
6568c2ecf20Sopenharmony_ci	fdtv->avc_data_length = ALIGN(3 + pos, 4);
6578c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	/* FIXME: check response code? */
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
6628c2ecf20Sopenharmony_ci
6638c2ecf20Sopenharmony_ci	if (ret == 0)
6648c2ecf20Sopenharmony_ci		msleep(50);
6658c2ecf20Sopenharmony_ci
6668c2ecf20Sopenharmony_ci	return ret;
6678c2ecf20Sopenharmony_ci}
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ciint avc_tuner_get_ts(struct firedtv *fdtv)
6708c2ecf20Sopenharmony_ci{
6718c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
6728c2ecf20Sopenharmony_ci	int ret, sl;
6738c2ecf20Sopenharmony_ci
6748c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
6778c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
6788c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_DSIT;
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci	sl = fdtv->type == FIREDTV_DVB_T ? 0x0c : 0x11;
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_ci	c->operand[0] = 0;	/* source plug */
6838c2ecf20Sopenharmony_ci	c->operand[1] = 0xd2;	/* subfunction replace */
6848c2ecf20Sopenharmony_ci	c->operand[2] = 0xff;	/* status */
6858c2ecf20Sopenharmony_ci	c->operand[3] = 0x20;	/* system id = DVB */
6868c2ecf20Sopenharmony_ci	c->operand[4] = 0x00;	/* antenna number */
6878c2ecf20Sopenharmony_ci	c->operand[5] = 0x0;	/* system_specific_search_flags */
6888c2ecf20Sopenharmony_ci	c->operand[6] = sl;	/* system_specific_multiplex selection_length */
6898c2ecf20Sopenharmony_ci	/*
6908c2ecf20Sopenharmony_ci	 * operand[7]: valid_flags[0]
6918c2ecf20Sopenharmony_ci	 * operand[8]: valid_flags[1]
6928c2ecf20Sopenharmony_ci	 * operand[7 + sl]: nr_of_dsit_sel_specs (always 0)
6938c2ecf20Sopenharmony_ci	 */
6948c2ecf20Sopenharmony_ci	clear_operands(c, 7, 24);
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	fdtv->avc_data_length = fdtv->type == FIREDTV_DVB_T ? 24 : 28;
6978c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
6988c2ecf20Sopenharmony_ci
6998c2ecf20Sopenharmony_ci	/* FIXME: check response code? */
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
7028c2ecf20Sopenharmony_ci
7038c2ecf20Sopenharmony_ci	if (ret == 0)
7048c2ecf20Sopenharmony_ci		msleep(250);
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	return ret;
7078c2ecf20Sopenharmony_ci}
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ciint avc_identify_subunit(struct firedtv *fdtv)
7108c2ecf20Sopenharmony_ci{
7118c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
7128c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
7138c2ecf20Sopenharmony_ci	int ret;
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
7188c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
7198c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_READ_DESCRIPTOR;
7208c2ecf20Sopenharmony_ci
7218c2ecf20Sopenharmony_ci	c->operand[0] = DESCRIPTOR_SUBUNIT_IDENTIFIER;
7228c2ecf20Sopenharmony_ci	c->operand[1] = 0xff;
7238c2ecf20Sopenharmony_ci	c->operand[2] = 0x00;
7248c2ecf20Sopenharmony_ci	c->operand[3] = 0x00; /* length highbyte */
7258c2ecf20Sopenharmony_ci	c->operand[4] = 0x08; /* length lowbyte  */
7268c2ecf20Sopenharmony_ci	c->operand[5] = 0x00; /* offset highbyte */
7278c2ecf20Sopenharmony_ci	c->operand[6] = 0x0d; /* offset lowbyte  */
7288c2ecf20Sopenharmony_ci	clear_operands(c, 7, 8); /* padding */
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
7318c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
7328c2ecf20Sopenharmony_ci	if (ret < 0)
7338c2ecf20Sopenharmony_ci		goto out;
7348c2ecf20Sopenharmony_ci
7358c2ecf20Sopenharmony_ci	if ((r->response != AVC_RESPONSE_STABLE &&
7368c2ecf20Sopenharmony_ci	     r->response != AVC_RESPONSE_ACCEPTED) ||
7378c2ecf20Sopenharmony_ci	    (r->operand[3] << 8) + r->operand[4] != 8) {
7388c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "cannot read subunit identifier\n");
7398c2ecf20Sopenharmony_ci		ret = -EINVAL;
7408c2ecf20Sopenharmony_ci	}
7418c2ecf20Sopenharmony_ciout:
7428c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_ci	return ret;
7458c2ecf20Sopenharmony_ci}
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci#define SIZEOF_ANTENNA_INPUT_INFO 22
7488c2ecf20Sopenharmony_ci
7498c2ecf20Sopenharmony_ciint avc_tuner_status(struct firedtv *fdtv, struct firedtv_tuner_status *stat)
7508c2ecf20Sopenharmony_ci{
7518c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
7528c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
7538c2ecf20Sopenharmony_ci	int length, ret;
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
7588c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
7598c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_READ_DESCRIPTOR;
7608c2ecf20Sopenharmony_ci
7618c2ecf20Sopenharmony_ci	c->operand[0] = DESCRIPTOR_TUNER_STATUS;
7628c2ecf20Sopenharmony_ci	c->operand[1] = 0xff;	/* read_result_status */
7638c2ecf20Sopenharmony_ci	/*
7648c2ecf20Sopenharmony_ci	 * operand[2]: reserved
7658c2ecf20Sopenharmony_ci	 * operand[3]: SIZEOF_ANTENNA_INPUT_INFO >> 8
7668c2ecf20Sopenharmony_ci	 * operand[4]: SIZEOF_ANTENNA_INPUT_INFO & 0xff
7678c2ecf20Sopenharmony_ci	 */
7688c2ecf20Sopenharmony_ci	clear_operands(c, 2, 31);
7698c2ecf20Sopenharmony_ci
7708c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
7718c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
7728c2ecf20Sopenharmony_ci	if (ret < 0)
7738c2ecf20Sopenharmony_ci		goto out;
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci	if (r->response != AVC_RESPONSE_STABLE &&
7768c2ecf20Sopenharmony_ci	    r->response != AVC_RESPONSE_ACCEPTED) {
7778c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "cannot read tuner status\n");
7788c2ecf20Sopenharmony_ci		ret = -EINVAL;
7798c2ecf20Sopenharmony_ci		goto out;
7808c2ecf20Sopenharmony_ci	}
7818c2ecf20Sopenharmony_ci
7828c2ecf20Sopenharmony_ci	length = r->operand[9];
7838c2ecf20Sopenharmony_ci	if (r->operand[1] != 0x10 || length != SIZEOF_ANTENNA_INPUT_INFO) {
7848c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "got invalid tuner status\n");
7858c2ecf20Sopenharmony_ci		ret = -EINVAL;
7868c2ecf20Sopenharmony_ci		goto out;
7878c2ecf20Sopenharmony_ci	}
7888c2ecf20Sopenharmony_ci
7898c2ecf20Sopenharmony_ci	stat->active_system		= r->operand[10];
7908c2ecf20Sopenharmony_ci	stat->searching			= r->operand[11] >> 7 & 1;
7918c2ecf20Sopenharmony_ci	stat->moving			= r->operand[11] >> 6 & 1;
7928c2ecf20Sopenharmony_ci	stat->no_rf			= r->operand[11] >> 5 & 1;
7938c2ecf20Sopenharmony_ci	stat->input			= r->operand[12] >> 7 & 1;
7948c2ecf20Sopenharmony_ci	stat->selected_antenna		= r->operand[12] & 0x7f;
7958c2ecf20Sopenharmony_ci	stat->ber			= r->operand[13] << 24 |
7968c2ecf20Sopenharmony_ci					  r->operand[14] << 16 |
7978c2ecf20Sopenharmony_ci					  r->operand[15] << 8 |
7988c2ecf20Sopenharmony_ci					  r->operand[16];
7998c2ecf20Sopenharmony_ci	stat->signal_strength		= r->operand[17];
8008c2ecf20Sopenharmony_ci	stat->raster_frequency		= r->operand[18] >> 6 & 2;
8018c2ecf20Sopenharmony_ci	stat->rf_frequency		= (r->operand[18] & 0x3f) << 16 |
8028c2ecf20Sopenharmony_ci					  r->operand[19] << 8 |
8038c2ecf20Sopenharmony_ci					  r->operand[20];
8048c2ecf20Sopenharmony_ci	stat->man_dep_info_length	= r->operand[21];
8058c2ecf20Sopenharmony_ci	stat->front_end_error		= r->operand[22] >> 4 & 1;
8068c2ecf20Sopenharmony_ci	stat->antenna_error		= r->operand[22] >> 3 & 1;
8078c2ecf20Sopenharmony_ci	stat->front_end_power_status	= r->operand[22] >> 1 & 1;
8088c2ecf20Sopenharmony_ci	stat->power_supply		= r->operand[22] & 1;
8098c2ecf20Sopenharmony_ci	stat->carrier_noise_ratio	= r->operand[23] << 8 |
8108c2ecf20Sopenharmony_ci					  r->operand[24];
8118c2ecf20Sopenharmony_ci	stat->power_supply_voltage	= r->operand[27];
8128c2ecf20Sopenharmony_ci	stat->antenna_voltage		= r->operand[28];
8138c2ecf20Sopenharmony_ci	stat->firewire_bus_voltage	= r->operand[29];
8148c2ecf20Sopenharmony_ci	stat->ca_mmi			= r->operand[30] & 1;
8158c2ecf20Sopenharmony_ci	stat->ca_pmt_reply		= r->operand[31] >> 7 & 1;
8168c2ecf20Sopenharmony_ci	stat->ca_date_time_request	= r->operand[31] >> 6 & 1;
8178c2ecf20Sopenharmony_ci	stat->ca_application_info	= r->operand[31] >> 5 & 1;
8188c2ecf20Sopenharmony_ci	stat->ca_module_present_status	= r->operand[31] >> 4 & 1;
8198c2ecf20Sopenharmony_ci	stat->ca_dvb_flag		= r->operand[31] >> 3 & 1;
8208c2ecf20Sopenharmony_ci	stat->ca_error_flag		= r->operand[31] >> 2 & 1;
8218c2ecf20Sopenharmony_ci	stat->ca_initialization_status	= r->operand[31] >> 1 & 1;
8228c2ecf20Sopenharmony_ciout:
8238c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	return ret;
8268c2ecf20Sopenharmony_ci}
8278c2ecf20Sopenharmony_ci
8288c2ecf20Sopenharmony_ciint avc_lnb_control(struct firedtv *fdtv, char voltage, char burst,
8298c2ecf20Sopenharmony_ci		    char conttone, char nrdiseq,
8308c2ecf20Sopenharmony_ci		    struct dvb_diseqc_master_cmd *diseqcmd)
8318c2ecf20Sopenharmony_ci{
8328c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
8338c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
8348c2ecf20Sopenharmony_ci	int pos, j, k, ret;
8358c2ecf20Sopenharmony_ci
8368c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
8378c2ecf20Sopenharmony_ci
8388c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
8398c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
8408c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
8438c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
8448c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
8458c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_LNB_CONTROL;
8468c2ecf20Sopenharmony_ci	c->operand[4] = voltage;
8478c2ecf20Sopenharmony_ci	c->operand[5] = nrdiseq;
8488c2ecf20Sopenharmony_ci
8498c2ecf20Sopenharmony_ci	pos = 6;
8508c2ecf20Sopenharmony_ci	for (j = 0; j < nrdiseq; j++) {
8518c2ecf20Sopenharmony_ci		c->operand[pos++] = diseqcmd[j].msg_len;
8528c2ecf20Sopenharmony_ci
8538c2ecf20Sopenharmony_ci		for (k = 0; k < diseqcmd[j].msg_len; k++)
8548c2ecf20Sopenharmony_ci			c->operand[pos++] = diseqcmd[j].msg[k];
8558c2ecf20Sopenharmony_ci	}
8568c2ecf20Sopenharmony_ci	c->operand[pos++] = burst;
8578c2ecf20Sopenharmony_ci	c->operand[pos++] = conttone;
8588c2ecf20Sopenharmony_ci	pad_operands(c, pos);
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci	fdtv->avc_data_length = ALIGN(3 + pos, 4);
8618c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
8628c2ecf20Sopenharmony_ci	if (ret < 0)
8638c2ecf20Sopenharmony_ci		goto out;
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_ci	if (r->response != AVC_RESPONSE_ACCEPTED) {
8668c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "LNB control failed\n");
8678c2ecf20Sopenharmony_ci		ret = -EINVAL;
8688c2ecf20Sopenharmony_ci	}
8698c2ecf20Sopenharmony_ciout:
8708c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
8718c2ecf20Sopenharmony_ci
8728c2ecf20Sopenharmony_ci	return ret;
8738c2ecf20Sopenharmony_ci}
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_ciint avc_register_remote_control(struct firedtv *fdtv)
8768c2ecf20Sopenharmony_ci{
8778c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
8788c2ecf20Sopenharmony_ci	int ret;
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
8818c2ecf20Sopenharmony_ci
8828c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_NOTIFY;
8838c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_UNIT | 7;
8848c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
8858c2ecf20Sopenharmony_ci
8868c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
8878c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
8888c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
8898c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_REGISTER_REMOTE_CONTROL;
8908c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* padding */
8918c2ecf20Sopenharmony_ci
8928c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 8;
8938c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci	/* FIXME: check response code? */
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
8988c2ecf20Sopenharmony_ci
8998c2ecf20Sopenharmony_ci	return ret;
9008c2ecf20Sopenharmony_ci}
9018c2ecf20Sopenharmony_ci
9028c2ecf20Sopenharmony_civoid avc_remote_ctrl_work(struct work_struct *work)
9038c2ecf20Sopenharmony_ci{
9048c2ecf20Sopenharmony_ci	struct firedtv *fdtv =
9058c2ecf20Sopenharmony_ci			container_of(work, struct firedtv, remote_ctrl_work);
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci	/* Should it be rescheduled in failure cases? */
9088c2ecf20Sopenharmony_ci	avc_register_remote_control(fdtv);
9098c2ecf20Sopenharmony_ci}
9108c2ecf20Sopenharmony_ci
9118c2ecf20Sopenharmony_ci#if 0 /* FIXME: unused */
9128c2ecf20Sopenharmony_ciint avc_tuner_host2ca(struct firedtv *fdtv)
9138c2ecf20Sopenharmony_ci{
9148c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
9158c2ecf20Sopenharmony_ci	int ret;
9168c2ecf20Sopenharmony_ci
9178c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
9188c2ecf20Sopenharmony_ci
9198c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
9208c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
9218c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
9228c2ecf20Sopenharmony_ci
9238c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
9248c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
9258c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
9268c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_HOST2CA;
9278c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
9288c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_APPLICATION_INFO; /* ca tag */
9298c2ecf20Sopenharmony_ci	clear_operands(c, 6, 8);
9308c2ecf20Sopenharmony_ci
9318c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
9328c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
9338c2ecf20Sopenharmony_ci
9348c2ecf20Sopenharmony_ci	/* FIXME: check response code? */
9358c2ecf20Sopenharmony_ci
9368c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
9378c2ecf20Sopenharmony_ci
9388c2ecf20Sopenharmony_ci	return ret;
9398c2ecf20Sopenharmony_ci}
9408c2ecf20Sopenharmony_ci#endif
9418c2ecf20Sopenharmony_ci
9428c2ecf20Sopenharmony_cistatic int get_ca_object_pos(struct avc_response_frame *r)
9438c2ecf20Sopenharmony_ci{
9448c2ecf20Sopenharmony_ci	int length = 1;
9458c2ecf20Sopenharmony_ci
9468c2ecf20Sopenharmony_ci	/* Check length of length field */
9478c2ecf20Sopenharmony_ci	if (r->operand[7] & 0x80)
9488c2ecf20Sopenharmony_ci		length = (r->operand[7] & 0x7f) + 1;
9498c2ecf20Sopenharmony_ci	return length + 7;
9508c2ecf20Sopenharmony_ci}
9518c2ecf20Sopenharmony_ci
9528c2ecf20Sopenharmony_cistatic int get_ca_object_length(struct avc_response_frame *r)
9538c2ecf20Sopenharmony_ci{
9548c2ecf20Sopenharmony_ci#if 0 /* FIXME: unused */
9558c2ecf20Sopenharmony_ci	int size = 0;
9568c2ecf20Sopenharmony_ci	int i;
9578c2ecf20Sopenharmony_ci
9588c2ecf20Sopenharmony_ci	if (r->operand[7] & 0x80)
9598c2ecf20Sopenharmony_ci		for (i = 0; i < (r->operand[7] & 0x7f); i++) {
9608c2ecf20Sopenharmony_ci			size <<= 8;
9618c2ecf20Sopenharmony_ci			size += r->operand[8 + i];
9628c2ecf20Sopenharmony_ci		}
9638c2ecf20Sopenharmony_ci#endif
9648c2ecf20Sopenharmony_ci	return r->operand[7];
9658c2ecf20Sopenharmony_ci}
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_ciint avc_ca_app_info(struct firedtv *fdtv, unsigned char *app_info,
9688c2ecf20Sopenharmony_ci		    unsigned int *len)
9698c2ecf20Sopenharmony_ci{
9708c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
9718c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
9728c2ecf20Sopenharmony_ci	int pos, ret;
9738c2ecf20Sopenharmony_ci
9748c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
9758c2ecf20Sopenharmony_ci
9768c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_STATUS;
9778c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
9788c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
9798c2ecf20Sopenharmony_ci
9808c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
9818c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
9828c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
9838c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_CA2HOST;
9848c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
9858c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_APPLICATION_INFO; /* ca tag */
9868c2ecf20Sopenharmony_ci	clear_operands(c, 6, LAST_OPERAND);
9878c2ecf20Sopenharmony_ci
9888c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
9898c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
9908c2ecf20Sopenharmony_ci	if (ret < 0)
9918c2ecf20Sopenharmony_ci		goto out;
9928c2ecf20Sopenharmony_ci
9938c2ecf20Sopenharmony_ci	/* FIXME: check response code and validate response data */
9948c2ecf20Sopenharmony_ci
9958c2ecf20Sopenharmony_ci	pos = get_ca_object_pos(r);
9968c2ecf20Sopenharmony_ci	app_info[0] = (EN50221_TAG_APP_INFO >> 16) & 0xff;
9978c2ecf20Sopenharmony_ci	app_info[1] = (EN50221_TAG_APP_INFO >>  8) & 0xff;
9988c2ecf20Sopenharmony_ci	app_info[2] = (EN50221_TAG_APP_INFO >>  0) & 0xff;
9998c2ecf20Sopenharmony_ci	app_info[3] = 6 + r->operand[pos + 4];
10008c2ecf20Sopenharmony_ci	app_info[4] = 0x01;
10018c2ecf20Sopenharmony_ci	memcpy(&app_info[5], &r->operand[pos], 5 + r->operand[pos + 4]);
10028c2ecf20Sopenharmony_ci	*len = app_info[3] + 4;
10038c2ecf20Sopenharmony_ciout:
10048c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
10058c2ecf20Sopenharmony_ci
10068c2ecf20Sopenharmony_ci	return ret;
10078c2ecf20Sopenharmony_ci}
10088c2ecf20Sopenharmony_ci
10098c2ecf20Sopenharmony_ciint avc_ca_info(struct firedtv *fdtv, unsigned char *app_info,
10108c2ecf20Sopenharmony_ci		unsigned int *len)
10118c2ecf20Sopenharmony_ci{
10128c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
10138c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
10148c2ecf20Sopenharmony_ci	int i, pos, ret;
10158c2ecf20Sopenharmony_ci
10168c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
10178c2ecf20Sopenharmony_ci
10188c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_STATUS;
10198c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
10208c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
10218c2ecf20Sopenharmony_ci
10228c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
10238c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
10248c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
10258c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_CA2HOST;
10268c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
10278c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_APPLICATION_INFO; /* ca tag */
10288c2ecf20Sopenharmony_ci	clear_operands(c, 6, LAST_OPERAND);
10298c2ecf20Sopenharmony_ci
10308c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
10318c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
10328c2ecf20Sopenharmony_ci	if (ret < 0)
10338c2ecf20Sopenharmony_ci		goto out;
10348c2ecf20Sopenharmony_ci
10358c2ecf20Sopenharmony_ci	/* FIXME: check response code and validate response data */
10368c2ecf20Sopenharmony_ci
10378c2ecf20Sopenharmony_ci	pos = get_ca_object_pos(r);
10388c2ecf20Sopenharmony_ci	app_info[0] = (EN50221_TAG_CA_INFO >> 16) & 0xff;
10398c2ecf20Sopenharmony_ci	app_info[1] = (EN50221_TAG_CA_INFO >>  8) & 0xff;
10408c2ecf20Sopenharmony_ci	app_info[2] = (EN50221_TAG_CA_INFO >>  0) & 0xff;
10418c2ecf20Sopenharmony_ci	if (num_fake_ca_system_ids == 0) {
10428c2ecf20Sopenharmony_ci		app_info[3] = 2;
10438c2ecf20Sopenharmony_ci		app_info[4] = r->operand[pos + 0];
10448c2ecf20Sopenharmony_ci		app_info[5] = r->operand[pos + 1];
10458c2ecf20Sopenharmony_ci	} else {
10468c2ecf20Sopenharmony_ci		app_info[3] = num_fake_ca_system_ids * 2;
10478c2ecf20Sopenharmony_ci		for (i = 0; i < num_fake_ca_system_ids; i++) {
10488c2ecf20Sopenharmony_ci			app_info[4 + i * 2] =
10498c2ecf20Sopenharmony_ci				(fake_ca_system_ids[i] >> 8) & 0xff;
10508c2ecf20Sopenharmony_ci			app_info[5 + i * 2] = fake_ca_system_ids[i] & 0xff;
10518c2ecf20Sopenharmony_ci		}
10528c2ecf20Sopenharmony_ci	}
10538c2ecf20Sopenharmony_ci	*len = app_info[3] + 4;
10548c2ecf20Sopenharmony_ciout:
10558c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
10568c2ecf20Sopenharmony_ci
10578c2ecf20Sopenharmony_ci	return ret;
10588c2ecf20Sopenharmony_ci}
10598c2ecf20Sopenharmony_ci
10608c2ecf20Sopenharmony_ciint avc_ca_reset(struct firedtv *fdtv)
10618c2ecf20Sopenharmony_ci{
10628c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
10638c2ecf20Sopenharmony_ci	int ret;
10648c2ecf20Sopenharmony_ci
10658c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
10668c2ecf20Sopenharmony_ci
10678c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
10688c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
10698c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
10708c2ecf20Sopenharmony_ci
10718c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
10728c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
10738c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
10748c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_HOST2CA;
10758c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
10768c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_RESET; /* ca tag */
10778c2ecf20Sopenharmony_ci	c->operand[6] = 0; /* more/last */
10788c2ecf20Sopenharmony_ci	c->operand[7] = 1; /* length */
10798c2ecf20Sopenharmony_ci	c->operand[8] = 0; /* force hardware reset */
10808c2ecf20Sopenharmony_ci
10818c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
10828c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
10838c2ecf20Sopenharmony_ci
10848c2ecf20Sopenharmony_ci	/* FIXME: check response code? */
10858c2ecf20Sopenharmony_ci
10868c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
10878c2ecf20Sopenharmony_ci
10888c2ecf20Sopenharmony_ci	return ret;
10898c2ecf20Sopenharmony_ci}
10908c2ecf20Sopenharmony_ci
10918c2ecf20Sopenharmony_ciint avc_ca_pmt(struct firedtv *fdtv, char *msg, int length)
10928c2ecf20Sopenharmony_ci{
10938c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
10948c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
10958c2ecf20Sopenharmony_ci	int list_management;
10968c2ecf20Sopenharmony_ci	int program_info_length;
10978c2ecf20Sopenharmony_ci	int pmt_cmd_id;
10988c2ecf20Sopenharmony_ci	int read_pos;
10998c2ecf20Sopenharmony_ci	int write_pos;
11008c2ecf20Sopenharmony_ci	int es_info_length;
11018c2ecf20Sopenharmony_ci	int crc32_csum;
11028c2ecf20Sopenharmony_ci	int ret;
11038c2ecf20Sopenharmony_ci
11048c2ecf20Sopenharmony_ci	if (unlikely(avc_debug & AVC_DEBUG_APPLICATION_PMT))
11058c2ecf20Sopenharmony_ci		debug_pmt(msg, length);
11068c2ecf20Sopenharmony_ci
11078c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
11088c2ecf20Sopenharmony_ci
11098c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_CONTROL;
11108c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
11118c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
11128c2ecf20Sopenharmony_ci
11138c2ecf20Sopenharmony_ci	if (msg[0] != EN50221_LIST_MANAGEMENT_ONLY) {
11148c2ecf20Sopenharmony_ci		dev_info(fdtv->device, "forcing list_management to ONLY\n");
11158c2ecf20Sopenharmony_ci		msg[0] = EN50221_LIST_MANAGEMENT_ONLY;
11168c2ecf20Sopenharmony_ci	}
11178c2ecf20Sopenharmony_ci	/* We take the cmd_id from the programme level only! */
11188c2ecf20Sopenharmony_ci	list_management = msg[0];
11198c2ecf20Sopenharmony_ci	program_info_length = ((msg[4] & 0x0f) << 8) + msg[5];
11208c2ecf20Sopenharmony_ci	if (program_info_length > 0)
11218c2ecf20Sopenharmony_ci		program_info_length--; /* Remove pmt_cmd_id */
11228c2ecf20Sopenharmony_ci	pmt_cmd_id = msg[6];
11238c2ecf20Sopenharmony_ci
11248c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
11258c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
11268c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
11278c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_HOST2CA;
11288c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
11298c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_PMT; /* ca tag */
11308c2ecf20Sopenharmony_ci	c->operand[6] = 0; /* more/last */
11318c2ecf20Sopenharmony_ci	/* Use three bytes for length field in case length > 127 */
11328c2ecf20Sopenharmony_ci	c->operand[10] = list_management;
11338c2ecf20Sopenharmony_ci	c->operand[11] = 0x01; /* pmt_cmd=OK_descramble */
11348c2ecf20Sopenharmony_ci
11358c2ecf20Sopenharmony_ci	/* TS program map table */
11368c2ecf20Sopenharmony_ci
11378c2ecf20Sopenharmony_ci	c->operand[12] = 0x02; /* Table id=2 */
11388c2ecf20Sopenharmony_ci	c->operand[13] = 0x80; /* Section syntax + length */
11398c2ecf20Sopenharmony_ci
11408c2ecf20Sopenharmony_ci	c->operand[15] = msg[1]; /* Program number */
11418c2ecf20Sopenharmony_ci	c->operand[16] = msg[2];
11428c2ecf20Sopenharmony_ci	c->operand[17] = msg[3]; /* Version number and current/next */
11438c2ecf20Sopenharmony_ci	c->operand[18] = 0x00; /* Section number=0 */
11448c2ecf20Sopenharmony_ci	c->operand[19] = 0x00; /* Last section number=0 */
11458c2ecf20Sopenharmony_ci	c->operand[20] = 0x1f; /* PCR_PID=1FFF */
11468c2ecf20Sopenharmony_ci	c->operand[21] = 0xff;
11478c2ecf20Sopenharmony_ci	c->operand[22] = (program_info_length >> 8); /* Program info length */
11488c2ecf20Sopenharmony_ci	c->operand[23] = (program_info_length & 0xff);
11498c2ecf20Sopenharmony_ci
11508c2ecf20Sopenharmony_ci	/* CA descriptors at programme level */
11518c2ecf20Sopenharmony_ci	read_pos = 6;
11528c2ecf20Sopenharmony_ci	write_pos = 24;
11538c2ecf20Sopenharmony_ci	if (program_info_length > 0) {
11548c2ecf20Sopenharmony_ci		pmt_cmd_id = msg[read_pos++];
11558c2ecf20Sopenharmony_ci		if (pmt_cmd_id != 1 && pmt_cmd_id != 4)
11568c2ecf20Sopenharmony_ci			dev_err(fdtv->device,
11578c2ecf20Sopenharmony_ci				"invalid pmt_cmd_id %d\n", pmt_cmd_id);
11588c2ecf20Sopenharmony_ci		if (program_info_length > sizeof(c->operand) - 4 - write_pos) {
11598c2ecf20Sopenharmony_ci			ret = -EINVAL;
11608c2ecf20Sopenharmony_ci			goto out;
11618c2ecf20Sopenharmony_ci		}
11628c2ecf20Sopenharmony_ci
11638c2ecf20Sopenharmony_ci		memcpy(&c->operand[write_pos], &msg[read_pos],
11648c2ecf20Sopenharmony_ci		       program_info_length);
11658c2ecf20Sopenharmony_ci		read_pos += program_info_length;
11668c2ecf20Sopenharmony_ci		write_pos += program_info_length;
11678c2ecf20Sopenharmony_ci	}
11688c2ecf20Sopenharmony_ci	while (read_pos + 4 < length) {
11698c2ecf20Sopenharmony_ci		if (write_pos + 4 >= sizeof(c->operand) - 4) {
11708c2ecf20Sopenharmony_ci			ret = -EINVAL;
11718c2ecf20Sopenharmony_ci			goto out;
11728c2ecf20Sopenharmony_ci		}
11738c2ecf20Sopenharmony_ci		c->operand[write_pos++] = msg[read_pos++];
11748c2ecf20Sopenharmony_ci		c->operand[write_pos++] = msg[read_pos++];
11758c2ecf20Sopenharmony_ci		c->operand[write_pos++] = msg[read_pos++];
11768c2ecf20Sopenharmony_ci		es_info_length =
11778c2ecf20Sopenharmony_ci			((msg[read_pos] & 0x0f) << 8) + msg[read_pos + 1];
11788c2ecf20Sopenharmony_ci		read_pos += 2;
11798c2ecf20Sopenharmony_ci		if (es_info_length > 0)
11808c2ecf20Sopenharmony_ci			es_info_length--; /* Remove pmt_cmd_id */
11818c2ecf20Sopenharmony_ci		c->operand[write_pos++] = es_info_length >> 8;
11828c2ecf20Sopenharmony_ci		c->operand[write_pos++] = es_info_length & 0xff;
11838c2ecf20Sopenharmony_ci		if (es_info_length > 0) {
11848c2ecf20Sopenharmony_ci			if (read_pos >= length) {
11858c2ecf20Sopenharmony_ci				ret = -EINVAL;
11868c2ecf20Sopenharmony_ci				goto out;
11878c2ecf20Sopenharmony_ci			}
11888c2ecf20Sopenharmony_ci			pmt_cmd_id = msg[read_pos++];
11898c2ecf20Sopenharmony_ci			if (pmt_cmd_id != 1 && pmt_cmd_id != 4)
11908c2ecf20Sopenharmony_ci				dev_err(fdtv->device, "invalid pmt_cmd_id %d at stream level\n",
11918c2ecf20Sopenharmony_ci					pmt_cmd_id);
11928c2ecf20Sopenharmony_ci
11938c2ecf20Sopenharmony_ci			if (es_info_length > sizeof(c->operand) - 4 - write_pos ||
11948c2ecf20Sopenharmony_ci			    es_info_length > length - read_pos) {
11958c2ecf20Sopenharmony_ci				ret = -EINVAL;
11968c2ecf20Sopenharmony_ci				goto out;
11978c2ecf20Sopenharmony_ci			}
11988c2ecf20Sopenharmony_ci
11998c2ecf20Sopenharmony_ci			memcpy(&c->operand[write_pos], &msg[read_pos],
12008c2ecf20Sopenharmony_ci			       es_info_length);
12018c2ecf20Sopenharmony_ci			read_pos += es_info_length;
12028c2ecf20Sopenharmony_ci			write_pos += es_info_length;
12038c2ecf20Sopenharmony_ci		}
12048c2ecf20Sopenharmony_ci	}
12058c2ecf20Sopenharmony_ci	write_pos += 4; /* CRC */
12068c2ecf20Sopenharmony_ci
12078c2ecf20Sopenharmony_ci	c->operand[7] = 0x82;
12088c2ecf20Sopenharmony_ci	c->operand[8] = (write_pos - 10) >> 8;
12098c2ecf20Sopenharmony_ci	c->operand[9] = (write_pos - 10) & 0xff;
12108c2ecf20Sopenharmony_ci	c->operand[14] = write_pos - 15;
12118c2ecf20Sopenharmony_ci
12128c2ecf20Sopenharmony_ci	crc32_csum = crc32_be(0, &c->operand[10], c->operand[12] - 1);
12138c2ecf20Sopenharmony_ci	c->operand[write_pos - 4] = (crc32_csum >> 24) & 0xff;
12148c2ecf20Sopenharmony_ci	c->operand[write_pos - 3] = (crc32_csum >> 16) & 0xff;
12158c2ecf20Sopenharmony_ci	c->operand[write_pos - 2] = (crc32_csum >>  8) & 0xff;
12168c2ecf20Sopenharmony_ci	c->operand[write_pos - 1] = (crc32_csum >>  0) & 0xff;
12178c2ecf20Sopenharmony_ci	pad_operands(c, write_pos);
12188c2ecf20Sopenharmony_ci
12198c2ecf20Sopenharmony_ci	fdtv->avc_data_length = ALIGN(3 + write_pos, 4);
12208c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
12218c2ecf20Sopenharmony_ci	if (ret < 0)
12228c2ecf20Sopenharmony_ci		goto out;
12238c2ecf20Sopenharmony_ci
12248c2ecf20Sopenharmony_ci	if (r->response != AVC_RESPONSE_ACCEPTED) {
12258c2ecf20Sopenharmony_ci		dev_err(fdtv->device,
12268c2ecf20Sopenharmony_ci			"CA PMT failed with response 0x%x\n", r->response);
12278c2ecf20Sopenharmony_ci		ret = -EACCES;
12288c2ecf20Sopenharmony_ci	}
12298c2ecf20Sopenharmony_ciout:
12308c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
12318c2ecf20Sopenharmony_ci
12328c2ecf20Sopenharmony_ci	return ret;
12338c2ecf20Sopenharmony_ci}
12348c2ecf20Sopenharmony_ci
12358c2ecf20Sopenharmony_ciint avc_ca_get_time_date(struct firedtv *fdtv, int *interval)
12368c2ecf20Sopenharmony_ci{
12378c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
12388c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
12398c2ecf20Sopenharmony_ci	int ret;
12408c2ecf20Sopenharmony_ci
12418c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
12428c2ecf20Sopenharmony_ci
12438c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_STATUS;
12448c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
12458c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
12468c2ecf20Sopenharmony_ci
12478c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
12488c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
12498c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
12508c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_CA2HOST;
12518c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
12528c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_DATE_TIME; /* ca tag */
12538c2ecf20Sopenharmony_ci	clear_operands(c, 6, LAST_OPERAND);
12548c2ecf20Sopenharmony_ci
12558c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
12568c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
12578c2ecf20Sopenharmony_ci	if (ret < 0)
12588c2ecf20Sopenharmony_ci		goto out;
12598c2ecf20Sopenharmony_ci
12608c2ecf20Sopenharmony_ci	/* FIXME: check response code and validate response data */
12618c2ecf20Sopenharmony_ci
12628c2ecf20Sopenharmony_ci	*interval = r->operand[get_ca_object_pos(r)];
12638c2ecf20Sopenharmony_ciout:
12648c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
12658c2ecf20Sopenharmony_ci
12668c2ecf20Sopenharmony_ci	return ret;
12678c2ecf20Sopenharmony_ci}
12688c2ecf20Sopenharmony_ci
12698c2ecf20Sopenharmony_ciint avc_ca_enter_menu(struct firedtv *fdtv)
12708c2ecf20Sopenharmony_ci{
12718c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
12728c2ecf20Sopenharmony_ci	int ret;
12738c2ecf20Sopenharmony_ci
12748c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
12758c2ecf20Sopenharmony_ci
12768c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_STATUS;
12778c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
12788c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
12798c2ecf20Sopenharmony_ci
12808c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
12818c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
12828c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
12838c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_HOST2CA;
12848c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
12858c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_ENTER_MENU;
12868c2ecf20Sopenharmony_ci	clear_operands(c, 6, 8);
12878c2ecf20Sopenharmony_ci
12888c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
12898c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
12908c2ecf20Sopenharmony_ci
12918c2ecf20Sopenharmony_ci	/* FIXME: check response code? */
12928c2ecf20Sopenharmony_ci
12938c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
12948c2ecf20Sopenharmony_ci
12958c2ecf20Sopenharmony_ci	return ret;
12968c2ecf20Sopenharmony_ci}
12978c2ecf20Sopenharmony_ci
12988c2ecf20Sopenharmony_ciint avc_ca_get_mmi(struct firedtv *fdtv, char *mmi_object, unsigned int *len)
12998c2ecf20Sopenharmony_ci{
13008c2ecf20Sopenharmony_ci	struct avc_command_frame *c = (void *)fdtv->avc_data;
13018c2ecf20Sopenharmony_ci	struct avc_response_frame *r = (void *)fdtv->avc_data;
13028c2ecf20Sopenharmony_ci	int ret;
13038c2ecf20Sopenharmony_ci
13048c2ecf20Sopenharmony_ci	mutex_lock(&fdtv->avc_mutex);
13058c2ecf20Sopenharmony_ci
13068c2ecf20Sopenharmony_ci	c->ctype   = AVC_CTYPE_STATUS;
13078c2ecf20Sopenharmony_ci	c->subunit = AVC_SUBUNIT_TYPE_TUNER | fdtv->subunit;
13088c2ecf20Sopenharmony_ci	c->opcode  = AVC_OPCODE_VENDOR;
13098c2ecf20Sopenharmony_ci
13108c2ecf20Sopenharmony_ci	c->operand[0] = SFE_VENDOR_DE_COMPANYID_0;
13118c2ecf20Sopenharmony_ci	c->operand[1] = SFE_VENDOR_DE_COMPANYID_1;
13128c2ecf20Sopenharmony_ci	c->operand[2] = SFE_VENDOR_DE_COMPANYID_2;
13138c2ecf20Sopenharmony_ci	c->operand[3] = SFE_VENDOR_OPCODE_CA2HOST;
13148c2ecf20Sopenharmony_ci	c->operand[4] = 0; /* slot */
13158c2ecf20Sopenharmony_ci	c->operand[5] = SFE_VENDOR_TAG_CA_MMI;
13168c2ecf20Sopenharmony_ci	clear_operands(c, 6, LAST_OPERAND);
13178c2ecf20Sopenharmony_ci
13188c2ecf20Sopenharmony_ci	fdtv->avc_data_length = 12;
13198c2ecf20Sopenharmony_ci	ret = avc_write(fdtv);
13208c2ecf20Sopenharmony_ci	if (ret < 0)
13218c2ecf20Sopenharmony_ci		goto out;
13228c2ecf20Sopenharmony_ci
13238c2ecf20Sopenharmony_ci	/* FIXME: check response code and validate response data */
13248c2ecf20Sopenharmony_ci
13258c2ecf20Sopenharmony_ci	*len = get_ca_object_length(r);
13268c2ecf20Sopenharmony_ci	memcpy(mmi_object, &r->operand[get_ca_object_pos(r)], *len);
13278c2ecf20Sopenharmony_ciout:
13288c2ecf20Sopenharmony_ci	mutex_unlock(&fdtv->avc_mutex);
13298c2ecf20Sopenharmony_ci
13308c2ecf20Sopenharmony_ci	return ret;
13318c2ecf20Sopenharmony_ci}
13328c2ecf20Sopenharmony_ci
13338c2ecf20Sopenharmony_ci#define CMP_OUTPUT_PLUG_CONTROL_REG_0	0xfffff0000904ULL
13348c2ecf20Sopenharmony_ci
13358c2ecf20Sopenharmony_cistatic int cmp_read(struct firedtv *fdtv, u64 addr, __be32 *data)
13368c2ecf20Sopenharmony_ci{
13378c2ecf20Sopenharmony_ci	int ret;
13388c2ecf20Sopenharmony_ci
13398c2ecf20Sopenharmony_ci	ret = fdtv_read(fdtv, addr, data);
13408c2ecf20Sopenharmony_ci	if (ret < 0)
13418c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "CMP: read I/O error\n");
13428c2ecf20Sopenharmony_ci
13438c2ecf20Sopenharmony_ci	return ret;
13448c2ecf20Sopenharmony_ci}
13458c2ecf20Sopenharmony_ci
13468c2ecf20Sopenharmony_cistatic int cmp_lock(struct firedtv *fdtv, u64 addr, __be32 data[])
13478c2ecf20Sopenharmony_ci{
13488c2ecf20Sopenharmony_ci	int ret;
13498c2ecf20Sopenharmony_ci
13508c2ecf20Sopenharmony_ci	ret = fdtv_lock(fdtv, addr, data);
13518c2ecf20Sopenharmony_ci	if (ret < 0)
13528c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "CMP: lock I/O error\n");
13538c2ecf20Sopenharmony_ci
13548c2ecf20Sopenharmony_ci	return ret;
13558c2ecf20Sopenharmony_ci}
13568c2ecf20Sopenharmony_ci
13578c2ecf20Sopenharmony_cistatic inline u32 get_opcr(__be32 opcr, u32 mask, u32 shift)
13588c2ecf20Sopenharmony_ci{
13598c2ecf20Sopenharmony_ci	return (be32_to_cpu(opcr) >> shift) & mask;
13608c2ecf20Sopenharmony_ci}
13618c2ecf20Sopenharmony_ci
13628c2ecf20Sopenharmony_cistatic inline void set_opcr(__be32 *opcr, u32 value, u32 mask, u32 shift)
13638c2ecf20Sopenharmony_ci{
13648c2ecf20Sopenharmony_ci	*opcr &= ~cpu_to_be32(mask << shift);
13658c2ecf20Sopenharmony_ci	*opcr |= cpu_to_be32((value & mask) << shift);
13668c2ecf20Sopenharmony_ci}
13678c2ecf20Sopenharmony_ci
13688c2ecf20Sopenharmony_ci#define get_opcr_online(v)		get_opcr((v), 0x1, 31)
13698c2ecf20Sopenharmony_ci#define get_opcr_p2p_connections(v)	get_opcr((v), 0x3f, 24)
13708c2ecf20Sopenharmony_ci#define get_opcr_channel(v)		get_opcr((v), 0x3f, 16)
13718c2ecf20Sopenharmony_ci
13728c2ecf20Sopenharmony_ci#define set_opcr_p2p_connections(p, v)	set_opcr((p), (v), 0x3f, 24)
13738c2ecf20Sopenharmony_ci#define set_opcr_channel(p, v)		set_opcr((p), (v), 0x3f, 16)
13748c2ecf20Sopenharmony_ci#define set_opcr_data_rate(p, v)	set_opcr((p), (v), 0x3, 14)
13758c2ecf20Sopenharmony_ci#define set_opcr_overhead_id(p, v)	set_opcr((p), (v), 0xf, 10)
13768c2ecf20Sopenharmony_ci
13778c2ecf20Sopenharmony_ciint cmp_establish_pp_connection(struct firedtv *fdtv, int plug, int channel)
13788c2ecf20Sopenharmony_ci{
13798c2ecf20Sopenharmony_ci	__be32 old_opcr, opcr[2];
13808c2ecf20Sopenharmony_ci	u64 opcr_address = CMP_OUTPUT_PLUG_CONTROL_REG_0 + (plug << 2);
13818c2ecf20Sopenharmony_ci	int attempts = 0;
13828c2ecf20Sopenharmony_ci	int ret;
13838c2ecf20Sopenharmony_ci
13848c2ecf20Sopenharmony_ci	ret = cmp_read(fdtv, opcr_address, opcr);
13858c2ecf20Sopenharmony_ci	if (ret < 0)
13868c2ecf20Sopenharmony_ci		return ret;
13878c2ecf20Sopenharmony_ci
13888c2ecf20Sopenharmony_cirepeat:
13898c2ecf20Sopenharmony_ci	if (!get_opcr_online(*opcr)) {
13908c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "CMP: output offline\n");
13918c2ecf20Sopenharmony_ci		return -EBUSY;
13928c2ecf20Sopenharmony_ci	}
13938c2ecf20Sopenharmony_ci
13948c2ecf20Sopenharmony_ci	old_opcr = *opcr;
13958c2ecf20Sopenharmony_ci
13968c2ecf20Sopenharmony_ci	if (get_opcr_p2p_connections(*opcr)) {
13978c2ecf20Sopenharmony_ci		if (get_opcr_channel(*opcr) != channel) {
13988c2ecf20Sopenharmony_ci			dev_err(fdtv->device, "CMP: cannot change channel\n");
13998c2ecf20Sopenharmony_ci			return -EBUSY;
14008c2ecf20Sopenharmony_ci		}
14018c2ecf20Sopenharmony_ci		dev_info(fdtv->device, "CMP: overlaying connection\n");
14028c2ecf20Sopenharmony_ci
14038c2ecf20Sopenharmony_ci		/* We don't allocate isochronous resources. */
14048c2ecf20Sopenharmony_ci	} else {
14058c2ecf20Sopenharmony_ci		set_opcr_channel(opcr, channel);
14068c2ecf20Sopenharmony_ci		set_opcr_data_rate(opcr, 2); /* S400 */
14078c2ecf20Sopenharmony_ci
14088c2ecf20Sopenharmony_ci		/* FIXME: this is for the worst case - optimize */
14098c2ecf20Sopenharmony_ci		set_opcr_overhead_id(opcr, 0);
14108c2ecf20Sopenharmony_ci
14118c2ecf20Sopenharmony_ci		/* FIXME: allocate isochronous channel and bandwidth at IRM */
14128c2ecf20Sopenharmony_ci	}
14138c2ecf20Sopenharmony_ci
14148c2ecf20Sopenharmony_ci	set_opcr_p2p_connections(opcr, get_opcr_p2p_connections(*opcr) + 1);
14158c2ecf20Sopenharmony_ci
14168c2ecf20Sopenharmony_ci	opcr[1] = *opcr;
14178c2ecf20Sopenharmony_ci	opcr[0] = old_opcr;
14188c2ecf20Sopenharmony_ci
14198c2ecf20Sopenharmony_ci	ret = cmp_lock(fdtv, opcr_address, opcr);
14208c2ecf20Sopenharmony_ci	if (ret < 0)
14218c2ecf20Sopenharmony_ci		return ret;
14228c2ecf20Sopenharmony_ci
14238c2ecf20Sopenharmony_ci	if (old_opcr != *opcr) {
14248c2ecf20Sopenharmony_ci		/*
14258c2ecf20Sopenharmony_ci		 * FIXME: if old_opcr.P2P_Connections > 0,
14268c2ecf20Sopenharmony_ci		 * deallocate isochronous channel and bandwidth at IRM
14278c2ecf20Sopenharmony_ci		 */
14288c2ecf20Sopenharmony_ci
14298c2ecf20Sopenharmony_ci		if (++attempts < 6) /* arbitrary limit */
14308c2ecf20Sopenharmony_ci			goto repeat;
14318c2ecf20Sopenharmony_ci		return -EBUSY;
14328c2ecf20Sopenharmony_ci	}
14338c2ecf20Sopenharmony_ci
14348c2ecf20Sopenharmony_ci	return 0;
14358c2ecf20Sopenharmony_ci}
14368c2ecf20Sopenharmony_ci
14378c2ecf20Sopenharmony_civoid cmp_break_pp_connection(struct firedtv *fdtv, int plug, int channel)
14388c2ecf20Sopenharmony_ci{
14398c2ecf20Sopenharmony_ci	__be32 old_opcr, opcr[2];
14408c2ecf20Sopenharmony_ci	u64 opcr_address = CMP_OUTPUT_PLUG_CONTROL_REG_0 + (plug << 2);
14418c2ecf20Sopenharmony_ci	int attempts = 0;
14428c2ecf20Sopenharmony_ci
14438c2ecf20Sopenharmony_ci	if (cmp_read(fdtv, opcr_address, opcr) < 0)
14448c2ecf20Sopenharmony_ci		return;
14458c2ecf20Sopenharmony_ci
14468c2ecf20Sopenharmony_cirepeat:
14478c2ecf20Sopenharmony_ci	if (!get_opcr_online(*opcr) || !get_opcr_p2p_connections(*opcr) ||
14488c2ecf20Sopenharmony_ci	    get_opcr_channel(*opcr) != channel) {
14498c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "CMP: no connection to break\n");
14508c2ecf20Sopenharmony_ci		return;
14518c2ecf20Sopenharmony_ci	}
14528c2ecf20Sopenharmony_ci
14538c2ecf20Sopenharmony_ci	old_opcr = *opcr;
14548c2ecf20Sopenharmony_ci	set_opcr_p2p_connections(opcr, get_opcr_p2p_connections(*opcr) - 1);
14558c2ecf20Sopenharmony_ci
14568c2ecf20Sopenharmony_ci	opcr[1] = *opcr;
14578c2ecf20Sopenharmony_ci	opcr[0] = old_opcr;
14588c2ecf20Sopenharmony_ci
14598c2ecf20Sopenharmony_ci	if (cmp_lock(fdtv, opcr_address, opcr) < 0)
14608c2ecf20Sopenharmony_ci		return;
14618c2ecf20Sopenharmony_ci
14628c2ecf20Sopenharmony_ci	if (old_opcr != *opcr) {
14638c2ecf20Sopenharmony_ci		/*
14648c2ecf20Sopenharmony_ci		 * FIXME: if old_opcr.P2P_Connections == 1, i.e. we were last
14658c2ecf20Sopenharmony_ci		 * owner, deallocate isochronous channel and bandwidth at IRM
14668c2ecf20Sopenharmony_ci		 * if (...)
14678c2ecf20Sopenharmony_ci		 *	fdtv->backend->dealloc_resources(fdtv, channel, bw);
14688c2ecf20Sopenharmony_ci		 */
14698c2ecf20Sopenharmony_ci
14708c2ecf20Sopenharmony_ci		if (++attempts < 6) /* arbitrary limit */
14718c2ecf20Sopenharmony_ci			goto repeat;
14728c2ecf20Sopenharmony_ci	}
14738c2ecf20Sopenharmony_ci}
1474