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 Henrik Kurelid <henrik@kurelid.se>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/device.h>
108c2ecf20Sopenharmony_ci#include <linux/dvb/ca.h>
118c2ecf20Sopenharmony_ci#include <linux/fs.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <media/dvbdev.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "firedtv.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define EN50221_TAG_APP_INFO_ENQUIRY	0x9f8020
198c2ecf20Sopenharmony_ci#define EN50221_TAG_CA_INFO_ENQUIRY	0x9f8030
208c2ecf20Sopenharmony_ci#define EN50221_TAG_CA_PMT		0x9f8032
218c2ecf20Sopenharmony_ci#define EN50221_TAG_ENTER_MENU		0x9f8022
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic int fdtv_ca_ready(struct firedtv_tuner_status *stat)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	return stat->ca_initialization_status	== 1 &&
268c2ecf20Sopenharmony_ci	       stat->ca_error_flag		== 0 &&
278c2ecf20Sopenharmony_ci	       stat->ca_dvb_flag		== 1 &&
288c2ecf20Sopenharmony_ci	       stat->ca_module_present_status	== 1;
298c2ecf20Sopenharmony_ci}
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int fdtv_get_ca_flags(struct firedtv_tuner_status *stat)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	int flags = 0;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	if (stat->ca_module_present_status == 1)
368c2ecf20Sopenharmony_ci		flags |= CA_CI_MODULE_PRESENT;
378c2ecf20Sopenharmony_ci	if (stat->ca_initialization_status == 1 &&
388c2ecf20Sopenharmony_ci	    stat->ca_error_flag            == 0 &&
398c2ecf20Sopenharmony_ci	    stat->ca_dvb_flag              == 1)
408c2ecf20Sopenharmony_ci		flags |= CA_CI_MODULE_READY;
418c2ecf20Sopenharmony_ci	return flags;
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic int fdtv_ca_get_caps(void *arg)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	struct ca_caps *cap = arg;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	cap->slot_num = 1;
498c2ecf20Sopenharmony_ci	cap->slot_type = CA_CI;
508c2ecf20Sopenharmony_ci	cap->descr_num = 1;
518c2ecf20Sopenharmony_ci	cap->descr_type = CA_ECD;
528c2ecf20Sopenharmony_ci	return 0;
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic int fdtv_ca_get_slot_info(struct firedtv *fdtv, void *arg)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	struct firedtv_tuner_status stat;
588c2ecf20Sopenharmony_ci	struct ca_slot_info *slot = arg;
598c2ecf20Sopenharmony_ci	int err;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	err = avc_tuner_status(fdtv, &stat);
628c2ecf20Sopenharmony_ci	if (err)
638c2ecf20Sopenharmony_ci		return err;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (slot->num != 0)
668c2ecf20Sopenharmony_ci		return -EACCES;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	slot->type = CA_CI;
698c2ecf20Sopenharmony_ci	slot->flags = fdtv_get_ca_flags(&stat);
708c2ecf20Sopenharmony_ci	return 0;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int fdtv_ca_app_info(struct firedtv *fdtv, void *arg)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	struct ca_msg *reply = arg;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	return avc_ca_app_info(fdtv, reply->msg, &reply->length);
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int fdtv_ca_info(struct firedtv *fdtv, void *arg)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	struct ca_msg *reply = arg;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return avc_ca_info(fdtv, reply->msg, &reply->length);
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic int fdtv_ca_get_mmi(struct firedtv *fdtv, void *arg)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	struct ca_msg *reply = arg;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	return avc_ca_get_mmi(fdtv, reply->msg, &reply->length);
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic int fdtv_ca_get_msg(struct firedtv *fdtv, void *arg)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct firedtv_tuner_status stat;
978c2ecf20Sopenharmony_ci	int err;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	switch (fdtv->ca_last_command) {
1008c2ecf20Sopenharmony_ci	case EN50221_TAG_APP_INFO_ENQUIRY:
1018c2ecf20Sopenharmony_ci		err = fdtv_ca_app_info(fdtv, arg);
1028c2ecf20Sopenharmony_ci		break;
1038c2ecf20Sopenharmony_ci	case EN50221_TAG_CA_INFO_ENQUIRY:
1048c2ecf20Sopenharmony_ci		err = fdtv_ca_info(fdtv, arg);
1058c2ecf20Sopenharmony_ci		break;
1068c2ecf20Sopenharmony_ci	default:
1078c2ecf20Sopenharmony_ci		err = avc_tuner_status(fdtv, &stat);
1088c2ecf20Sopenharmony_ci		if (err)
1098c2ecf20Sopenharmony_ci			break;
1108c2ecf20Sopenharmony_ci		if (stat.ca_mmi == 1)
1118c2ecf20Sopenharmony_ci			err = fdtv_ca_get_mmi(fdtv, arg);
1128c2ecf20Sopenharmony_ci		else {
1138c2ecf20Sopenharmony_ci			dev_info(fdtv->device, "unhandled CA message 0x%08x\n",
1148c2ecf20Sopenharmony_ci				 fdtv->ca_last_command);
1158c2ecf20Sopenharmony_ci			err = -EACCES;
1168c2ecf20Sopenharmony_ci		}
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci	fdtv->ca_last_command = 0;
1198c2ecf20Sopenharmony_ci	return err;
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic int fdtv_ca_pmt(struct firedtv *fdtv, void *arg)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	struct ca_msg *msg = arg;
1258c2ecf20Sopenharmony_ci	int data_pos;
1268c2ecf20Sopenharmony_ci	int data_length;
1278c2ecf20Sopenharmony_ci	int i;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	data_pos = 4;
1308c2ecf20Sopenharmony_ci	if (msg->msg[3] & 0x80) {
1318c2ecf20Sopenharmony_ci		data_length = 0;
1328c2ecf20Sopenharmony_ci		for (i = 0; i < (msg->msg[3] & 0x7f); i++)
1338c2ecf20Sopenharmony_ci			data_length = (data_length << 8) + msg->msg[data_pos++];
1348c2ecf20Sopenharmony_ci	} else {
1358c2ecf20Sopenharmony_ci		data_length = msg->msg[3];
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci	if (data_length > sizeof(msg->msg) - data_pos)
1388c2ecf20Sopenharmony_ci		return -EINVAL;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	return avc_ca_pmt(fdtv, &msg->msg[data_pos], data_length);
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int fdtv_ca_send_msg(struct firedtv *fdtv, void *arg)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	struct ca_msg *msg = arg;
1468c2ecf20Sopenharmony_ci	int err;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/* Do we need a semaphore for this? */
1498c2ecf20Sopenharmony_ci	fdtv->ca_last_command =
1508c2ecf20Sopenharmony_ci		(msg->msg[0] << 16) + (msg->msg[1] << 8) + msg->msg[2];
1518c2ecf20Sopenharmony_ci	switch (fdtv->ca_last_command) {
1528c2ecf20Sopenharmony_ci	case EN50221_TAG_CA_PMT:
1538c2ecf20Sopenharmony_ci		err = fdtv_ca_pmt(fdtv, arg);
1548c2ecf20Sopenharmony_ci		break;
1558c2ecf20Sopenharmony_ci	case EN50221_TAG_APP_INFO_ENQUIRY:
1568c2ecf20Sopenharmony_ci		/* handled in ca_get_msg */
1578c2ecf20Sopenharmony_ci		err = 0;
1588c2ecf20Sopenharmony_ci		break;
1598c2ecf20Sopenharmony_ci	case EN50221_TAG_CA_INFO_ENQUIRY:
1608c2ecf20Sopenharmony_ci		/* handled in ca_get_msg */
1618c2ecf20Sopenharmony_ci		err = 0;
1628c2ecf20Sopenharmony_ci		break;
1638c2ecf20Sopenharmony_ci	case EN50221_TAG_ENTER_MENU:
1648c2ecf20Sopenharmony_ci		err = avc_ca_enter_menu(fdtv);
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci	default:
1678c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "unhandled CA message 0x%08x\n",
1688c2ecf20Sopenharmony_ci			fdtv->ca_last_command);
1698c2ecf20Sopenharmony_ci		err = -EACCES;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	return err;
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic int fdtv_ca_ioctl(struct file *file, unsigned int cmd, void *arg)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	struct dvb_device *dvbdev = file->private_data;
1778c2ecf20Sopenharmony_ci	struct firedtv *fdtv = dvbdev->priv;
1788c2ecf20Sopenharmony_ci	struct firedtv_tuner_status stat;
1798c2ecf20Sopenharmony_ci	int err;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	switch (cmd) {
1828c2ecf20Sopenharmony_ci	case CA_RESET:
1838c2ecf20Sopenharmony_ci		err = avc_ca_reset(fdtv);
1848c2ecf20Sopenharmony_ci		break;
1858c2ecf20Sopenharmony_ci	case CA_GET_CAP:
1868c2ecf20Sopenharmony_ci		err = fdtv_ca_get_caps(arg);
1878c2ecf20Sopenharmony_ci		break;
1888c2ecf20Sopenharmony_ci	case CA_GET_SLOT_INFO:
1898c2ecf20Sopenharmony_ci		err = fdtv_ca_get_slot_info(fdtv, arg);
1908c2ecf20Sopenharmony_ci		break;
1918c2ecf20Sopenharmony_ci	case CA_GET_MSG:
1928c2ecf20Sopenharmony_ci		err = fdtv_ca_get_msg(fdtv, arg);
1938c2ecf20Sopenharmony_ci		break;
1948c2ecf20Sopenharmony_ci	case CA_SEND_MSG:
1958c2ecf20Sopenharmony_ci		err = fdtv_ca_send_msg(fdtv, arg);
1968c2ecf20Sopenharmony_ci		break;
1978c2ecf20Sopenharmony_ci	default:
1988c2ecf20Sopenharmony_ci		dev_info(fdtv->device, "unhandled CA ioctl %u\n", cmd);
1998c2ecf20Sopenharmony_ci		err = -EOPNOTSUPP;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	/* FIXME Is this necessary? */
2038c2ecf20Sopenharmony_ci	avc_tuner_status(fdtv, &stat);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return err;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic __poll_t fdtv_ca_io_poll(struct file *file, poll_table *wait)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	return EPOLLIN;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_cistatic const struct file_operations fdtv_ca_fops = {
2148c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
2158c2ecf20Sopenharmony_ci	.unlocked_ioctl	= dvb_generic_ioctl,
2168c2ecf20Sopenharmony_ci	.open		= dvb_generic_open,
2178c2ecf20Sopenharmony_ci	.release	= dvb_generic_release,
2188c2ecf20Sopenharmony_ci	.poll		= fdtv_ca_io_poll,
2198c2ecf20Sopenharmony_ci	.llseek		= noop_llseek,
2208c2ecf20Sopenharmony_ci};
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic const struct dvb_device fdtv_ca = {
2238c2ecf20Sopenharmony_ci	.users		= 1,
2248c2ecf20Sopenharmony_ci	.readers	= 1,
2258c2ecf20Sopenharmony_ci	.writers	= 1,
2268c2ecf20Sopenharmony_ci	.fops		= &fdtv_ca_fops,
2278c2ecf20Sopenharmony_ci	.kernel_ioctl	= fdtv_ca_ioctl,
2288c2ecf20Sopenharmony_ci};
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ciint fdtv_ca_register(struct firedtv *fdtv)
2318c2ecf20Sopenharmony_ci{
2328c2ecf20Sopenharmony_ci	struct firedtv_tuner_status stat;
2338c2ecf20Sopenharmony_ci	int err;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	if (avc_tuner_status(fdtv, &stat))
2368c2ecf20Sopenharmony_ci		return -EINVAL;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	if (!fdtv_ca_ready(&stat))
2398c2ecf20Sopenharmony_ci		return -EFAULT;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	err = dvb_register_device(&fdtv->adapter, &fdtv->cadev,
2428c2ecf20Sopenharmony_ci				  &fdtv_ca, fdtv, DVB_DEVICE_CA, 0);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	if (stat.ca_application_info == 0)
2458c2ecf20Sopenharmony_ci		dev_err(fdtv->device, "CaApplicationInfo is not set\n");
2468c2ecf20Sopenharmony_ci	if (stat.ca_date_time_request == 1)
2478c2ecf20Sopenharmony_ci		avc_ca_get_time_date(fdtv, &fdtv->ca_time_interval);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	return err;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_civoid fdtv_ca_release(struct firedtv *fdtv)
2538c2ecf20Sopenharmony_ci{
2548c2ecf20Sopenharmony_ci	dvb_unregister_device(fdtv->cadev);
2558c2ecf20Sopenharmony_ci}
256