blob: ca5908d4674ffb2c81d5514403a3f36de655a5b3 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
/*#include <linux/firmware.h>*/
#include <linux/err.h>
/*#include <linux/clk.h>*/
#include <linux/of_device.h>
#include <linux/of_reserved_mem.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
/*#include <linux/gpio/consumer.h>*/
#include <linux/i2c.h>
#include <linux/gpio.h>
#include "demod_func.h"
#include "dvbs.h"
#include "dvbs_diseqc.h"
struct aml_def_diseqc_lnb aml_lnb;
static int diseqc_debug;
static unsigned int diseqc_attached;
#define AML_DISEQC_VER "20201225\n"
#define DISEQC_EN_ON_DELAY 60
#define dprintk(level, fmt, arg...) \
do { \
if (diseqc_debug >= (level)) \
pr_info("diseqc: " fmt, ## arg); \
} while (0)
#define aml_lnb_sgm41286
u32 sendburst_on;
u32 diseqc_cmd_bypass;
void aml_diseqc_dbg_en(unsigned int val)
{
diseqc_debug = val;
}
void aml_def_set_lnb_en(int high_low)
{
if (IS_ERR_OR_NULL(aml_lnb.gpio_lnb_en)) {
dprintk(0, "err lnb en gpio dest\n");
return;
}
if (aml_lnb.lnb_en == high_low)
return;
aml_lnb.lnb_en = high_low;
if (high_low)
gpiod_set_value(aml_lnb.gpio_lnb_en, 1);
else
gpiod_set_value(aml_lnb.gpio_lnb_en, 0);
dprintk(1, "lnb en:%d\n", high_low);
}
void aml_def_set_lnb_sel(int high_low)
{
if (IS_ERR_OR_NULL(aml_lnb.gpio_lnb_sel)) {
dprintk(0, "err lnb sel gpio dest\n");
return;
}
if (aml_lnb.lnb_sel == high_low)
return;
aml_lnb.lnb_sel = high_low;
if (high_low)
gpiod_set_value(aml_lnb.gpio_lnb_sel, 1);
else
gpiod_set_value(aml_lnb.gpio_lnb_sel, 0);
dprintk(1, "lnb sel:%d\n", high_low);
}
void aml_diseqc_toneburst_sa(void)
{
/* tone burst a is 12.5ms continus tone*/
dvbs2_diseqc_continuous_tone(1);
mdelay(13);
dvbs2_diseqc_continuous_tone(0);
}
void aml_diseqc_toneburst_sb(void)
{
struct dvb_diseqc_master_cmd cmd;
cmd.msg_len = 1;
cmd.msg[0] = 0xff;
cmd.msg[1] = 0xff;
dvbs2_diseqc_send_msg(cmd.msg_len, &cmd.msg[0]);
}
void aml_diseqc_tone_on(u32 onoff)
{
if (onoff)
dvbs2_diseqc_continuous_tone(1);
else
dvbs2_diseqc_continuous_tone(0);
dprintk(0, "%s %d\n", __func__, onoff);
}
void aml_diseqc_flag_tone_on(u32 onoff)
{
struct aml_def_diseqc_lnb *lnb = &aml_lnb;
lnb->tone_on = onoff;
}
u32 aml_diseqc_send_cmd(struct dvb_diseqc_master_cmd *cmd)
{
unsigned int timeout;
unsigned long ret = 0;
struct aml_def_diseqc_lnb *lnb = &aml_lnb;
reinit_completion(&lnb->tx_msg_ok);
/* disable continuous tone */
aml_diseqc_tone_on(0);
if (lnb->tone_on)
mdelay(12);
mutex_lock(&lnb->demod->mutex_tx_msg);
/*PR_INFO("diseqc_send_master_cmd empty func call\n");*/
timeout = msecs_to_jiffies(2000);
if (cmd->msg_len)
dvbs2_diseqc_send_msg(cmd->msg_len, cmd->msg);
else
goto cmd_exit;
ret = wait_for_completion_timeout(&lnb->tx_msg_ok, timeout);
if (ret <= 0) /* time out */
dprintk(0, "send cmd time out\n");
/* Send burst SA or SB */
if (sendburst_on && cmd->msg_len == 4 && cmd->msg[2] == 0x38 &&
cmd->msg[3] >= 0xf0) {
mdelay(16);
if ((cmd->msg[3] >= 0xf0 && cmd->msg[3] <= 0xf3) &&
(cmd->msg[3] >= 0xf8 && cmd->msg[3] <= 0xfb))
aml_diseqc_toneburst_sa();
else
aml_diseqc_toneburst_sb();
dprintk(1, "burst\n");
}
/* Is tone on, need set tone on */
if (lnb->tone_on)
mdelay(16);
aml_diseqc_tone_on(lnb->tone_on);
cmd_exit:
dprintk(0, "%s burst_on:%d\n", __func__, sendburst_on);
mutex_unlock(&lnb->demod->mutex_tx_msg);
return ret;
}
int aml_diseqc_send_burst(struct dvb_frontend *fe,
enum fe_sec_mini_cmd minicmd)
{
dprintk(0, "%s burst-%d\n", __func__, minicmd);
if (minicmd == SEC_MINI_A)
aml_diseqc_toneburst_sa();
else
aml_diseqc_toneburst_sb();
return 0;
}
#ifdef aml_lnb_sgm41286
/*
* This callback function to implement the
* FE_SET_TONE ioctl (only Satellite).
*
*
*/
int aml_diseqc_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone)
{
struct aml_def_diseqc_lnb *lnb = &aml_lnb;
if (diseqc_cmd_bypass)
return 1;
dprintk(1, "%s: %d\n", __func__, tone ? 0 : 1);
switch (tone) {
case SEC_TONE_OFF:
aml_diseqc_flag_tone_on(0);
aml_diseqc_tone_on(0);
if (lnb->voltage != SEC_VOLTAGE_OFF)
mdelay(23);
break;
case SEC_TONE_ON:
aml_diseqc_flag_tone_on(1);
aml_diseqc_tone_on(1);
if (lnb->voltage != SEC_VOLTAGE_OFF)
mdelay(DISEQC_EN_ON_DELAY);
break;
default:
return -EINVAL;
}
return 0;
}
/*
* This callback function to implement the
* FE_SET_VOLTAGE ioctl (only Satellite).
*
*
*/
int aml_diseqc_set_voltage(struct dvb_frontend *fe,
enum fe_sec_voltage voltage)
{
struct aml_def_diseqc_lnb *lnb = &aml_lnb;
dprintk(1, "%s: %d\n", __func__, voltage);
lnb->voltage = voltage;
switch (voltage) {
case SEC_VOLTAGE_OFF:
aml_def_set_lnb_en(0);
aml_def_set_lnb_sel(0);
mdelay(20);
break;
case SEC_VOLTAGE_13:
aml_def_set_lnb_en(1);
aml_def_set_lnb_sel(0);
mdelay(DISEQC_EN_ON_DELAY);
break;
case SEC_VOLTAGE_18:
aml_def_set_lnb_en(1);
aml_def_set_lnb_sel(1);
mdelay(DISEQC_EN_ON_DELAY);
break;
default:
return -EINVAL;
}
return 0;
}
/*
* This callback function to implement the
* FE_DISEQC_SEND_MASTER_CMD ioctl (only Satellite).
*
*
*/
int aml_diseqc_send_master_cmd(struct dvb_frontend *fe,
struct dvb_diseqc_master_cmd *cmd)
{
int ret;
u32 i, size;
unsigned char log_buf[128] = {0};
if (diseqc_cmd_bypass)
return 1;
if (cmd->msg_len <= 0)
return -EINVAL;
dprintk(1, "%s\n", __func__);
size = sprintf(log_buf, "tx len %d:", cmd->msg_len);
for (i = 0; i < cmd->msg_len; i++)
size += sprintf(log_buf + size, " %02x", cmd->msg[i]);
size += sprintf(log_buf + size, "\n");
log_buf[size] = '\0';
dprintk(1, "%s", log_buf);
ret = aml_diseqc_send_cmd(cmd);
return 0;
}
/*
* callback function to implement the
* FE_ENABLE_HIGH_LNB_VOLTAGE ioctl (only Satellite).
*
*/
int
aml_diseqc_en_high_lnb_voltage(struct dvb_frontend *fe, long arg)
{
/*struct aml_def_diseqc_lnb *lnb = &aml_lnb;*/
dprintk(1, "%s %d\n", __func__, (u32)arg);
if (arg) {
aml_def_set_lnb_en(1);
aml_def_set_lnb_sel(1);
mdelay(DISEQC_EN_ON_DELAY);
} else {
aml_def_set_lnb_en(1);
aml_def_set_lnb_sel(0);
}
return 0;
}
/*
* callback function requesting that the Satelite Equipment
* Control (SEC) driver to release and free any memory
*
*/
void aml_diseqc_release_sec(struct dvb_frontend *fe)
{
aml_diseqc_set_voltage(fe, SEC_VOLTAGE_OFF);
dprintk(1, "diseqc release\n");
/* if necessary free memory*/
}
#endif
void aml_diseqc_attach(struct amldtvdemod_device_s *devp,
struct dvb_frontend *fe)
{
struct aml_def_diseqc_lnb *diseqc = &aml_lnb;
if (diseqc_attached) {
dprintk(0, "diseqc had attached\n");
return;
}
memset(&aml_lnb, 0, sizeof(struct aml_def_diseqc_lnb));
diseqc->demod = devp;
init_completion(&diseqc->tx_msg_ok);
init_completion(&diseqc->rx_msg_ok);
diseqc->gpio_lnb_en = NULL;
diseqc->gpio_lnb_sel = NULL;
if (!strcmp(devp->diseqc_name, "sgm_41286")) {
diseqc = &aml_lnb;
/*
* Diseqc 1.x compatible
*/
diseqc->gpio_lnb_en = devm_gpiod_get(devp->dev, "lnb_en",
GPIOD_OUT_LOW);
if (IS_ERR_OR_NULL(diseqc->gpio_lnb_en))
dprintk(0, "get lnb en fail\n");
diseqc->gpio_lnb_sel = devm_gpiod_get(devp->dev, "lnb_sel",
GPIOD_OUT_LOW);
if (IS_ERR_OR_NULL(diseqc->gpio_lnb_sel))
dprintk(0, "get lnb sel fail\n");
fe->ops.release_sec = aml_diseqc_release_sec;
fe->ops.set_tone = aml_diseqc_set_tone;
fe->ops.set_voltage = aml_diseqc_set_voltage;
fe->ops.diseqc_send_master_cmd = aml_diseqc_send_master_cmd;
fe->ops.enable_high_lnb_voltage = aml_diseqc_en_high_lnb_voltage;
fe->ops.diseqc_send_burst = aml_diseqc_send_burst;
diseqc->lnb_en = 1;
diseqc->lnb_sel = 1;
aml_def_set_lnb_en(0);
aml_def_set_lnb_sel(0);
/*aml_diseqc_tone_on(0);*/
}
diseqc_attached = 1;
dprintk(0, "%s flag:%d\n", __func__, diseqc_attached);
}
void aml_diseqc_isr(void)
{
unsigned int diseq_irq_sts = 0;
struct aml_def_diseqc_lnb *lnb = &aml_lnb;
diseq_irq_sts = dvbs2_diseqc_irq_check();
if (diseq_irq_sts) {
if (diseq_irq_sts & IRQ_STS_GAPBURST)
dprintk(1, "isr IRQGAPBURST\n");
if (diseq_irq_sts & IRQ_STS_TXFIFO64B)
dprintk(1, "isr IRQTXFIFO64B\n");
if (diseq_irq_sts & IRQ_STS_TXEND) {
complete(&lnb->tx_msg_ok);
dprintk(1, "isr IRQTXEND\n");
}
if (diseq_irq_sts & IRQ_STS_TIMEOUT)
dprintk(1, "isr IRQTIMEOUT\n");
if (diseq_irq_sts & IRQ_STS_TRFINISH)
dprintk(1, "isr IRQTRFINISH\n");
if (diseq_irq_sts & IRQ_STS_RXFIFO8B)
dprintk(1, "isr IRQRXFIFO8B\n");
if (diseq_irq_sts & IRQ_STS_RXEND)
dprintk(1, "isr IRQRXEND\n");
}
}
void aml_diseqc_status(void)
{
struct aml_def_diseqc_lnb *lnb = &aml_lnb;
dprintk(0, "%s\n", AML_DISEQC_VER);
dprintk(0, "lnb_en=%d\n", lnb->lnb_en);
dprintk(0, "lnb_sel=%d\n", lnb->lnb_sel);
dprintk(0, "tone_on=%d\n", lnb->tone_on);
dprintk(0, "voltage=%d\n", lnb->voltage);
}