18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci	Intersil ISL6423 SEC and LNB Power supply controller
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci	Copyright (C) Manu Abraham <abraham.manu@gmail.com>
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci*/
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/delay.h>
108c2ecf20Sopenharmony_ci#include <linux/errno.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/string.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h>
188c2ecf20Sopenharmony_ci#include "isl6423.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic unsigned int verbose;
218c2ecf20Sopenharmony_cimodule_param(verbose, int, 0644);
228c2ecf20Sopenharmony_ciMODULE_PARM_DESC(verbose, "Set Verbosity level");
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define FE_ERROR				0
258c2ecf20Sopenharmony_ci#define FE_NOTICE				1
268c2ecf20Sopenharmony_ci#define FE_INFO					2
278c2ecf20Sopenharmony_ci#define FE_DEBUG				3
288c2ecf20Sopenharmony_ci#define FE_DEBUGREG				4
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define dprintk(__y, __z, format, arg...) do {						\
318c2ecf20Sopenharmony_ci	if (__z) {									\
328c2ecf20Sopenharmony_ci		if	((verbose > FE_ERROR) && (verbose > __y))			\
338c2ecf20Sopenharmony_ci			printk(KERN_ERR "%s: " format "\n", __func__ , ##arg);		\
348c2ecf20Sopenharmony_ci		else if	((verbose > FE_NOTICE) && (verbose > __y))			\
358c2ecf20Sopenharmony_ci			printk(KERN_NOTICE "%s: " format "\n", __func__ , ##arg);	\
368c2ecf20Sopenharmony_ci		else if ((verbose > FE_INFO) && (verbose > __y))			\
378c2ecf20Sopenharmony_ci			printk(KERN_INFO "%s: " format "\n", __func__ , ##arg);		\
388c2ecf20Sopenharmony_ci		else if ((verbose > FE_DEBUG) && (verbose > __y))			\
398c2ecf20Sopenharmony_ci			printk(KERN_DEBUG "%s: " format "\n", __func__ , ##arg);	\
408c2ecf20Sopenharmony_ci	} else {									\
418c2ecf20Sopenharmony_ci		if (verbose > __y)							\
428c2ecf20Sopenharmony_ci			printk(format, ##arg);						\
438c2ecf20Sopenharmony_ci	}										\
448c2ecf20Sopenharmony_ci} while (0)
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistruct isl6423_dev {
478c2ecf20Sopenharmony_ci	const struct isl6423_config	*config;
488c2ecf20Sopenharmony_ci	struct i2c_adapter		*i2c;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	u8 reg_3;
518c2ecf20Sopenharmony_ci	u8 reg_4;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	unsigned int verbose;
548c2ecf20Sopenharmony_ci};
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic int isl6423_write(struct isl6423_dev *isl6423, u8 reg)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct i2c_adapter *i2c = isl6423->i2c;
598c2ecf20Sopenharmony_ci	u8 addr			= isl6423->config->addr;
608c2ecf20Sopenharmony_ci	int err = 0;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = &reg, .len = 1 };
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	dprintk(FE_DEBUG, 1, "write reg %02X", reg);
658c2ecf20Sopenharmony_ci	err = i2c_transfer(i2c, &msg, 1);
668c2ecf20Sopenharmony_ci	if (err < 0)
678c2ecf20Sopenharmony_ci		goto exit;
688c2ecf20Sopenharmony_ci	return 0;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ciexit:
718c2ecf20Sopenharmony_ci	dprintk(FE_ERROR, 1, "I/O error <%d>", err);
728c2ecf20Sopenharmony_ci	return err;
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic int isl6423_set_modulation(struct dvb_frontend *fe)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct isl6423_dev *isl6423		= (struct isl6423_dev *) fe->sec_priv;
788c2ecf20Sopenharmony_ci	const struct isl6423_config *config	= isl6423->config;
798c2ecf20Sopenharmony_ci	int err = 0;
808c2ecf20Sopenharmony_ci	u8 reg_2 = 0;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	reg_2 = 0x01 << 5;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (config->mod_extern)
858c2ecf20Sopenharmony_ci		reg_2 |= (1 << 3);
868c2ecf20Sopenharmony_ci	else
878c2ecf20Sopenharmony_ci		reg_2 |= (1 << 4);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_2);
908c2ecf20Sopenharmony_ci	if (err < 0)
918c2ecf20Sopenharmony_ci		goto exit;
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ciexit:
958c2ecf20Sopenharmony_ci	dprintk(FE_ERROR, 1, "I/O error <%d>", err);
968c2ecf20Sopenharmony_ci	return err;
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic int isl6423_voltage_boost(struct dvb_frontend *fe, long arg)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	struct isl6423_dev *isl6423 = (struct isl6423_dev *) fe->sec_priv;
1028c2ecf20Sopenharmony_ci	u8 reg_3 = isl6423->reg_3;
1038c2ecf20Sopenharmony_ci	u8 reg_4 = isl6423->reg_4;
1048c2ecf20Sopenharmony_ci	int err = 0;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if (arg) {
1078c2ecf20Sopenharmony_ci		/* EN = 1, VSPEN = 1, VBOT = 1 */
1088c2ecf20Sopenharmony_ci		reg_4 |= (1 << 4);
1098c2ecf20Sopenharmony_ci		reg_4 |= 0x1;
1108c2ecf20Sopenharmony_ci		reg_3 |= (1 << 3);
1118c2ecf20Sopenharmony_ci	} else {
1128c2ecf20Sopenharmony_ci		/* EN = 1, VSPEN = 1, VBOT = 0 */
1138c2ecf20Sopenharmony_ci		reg_4 |= (1 << 4);
1148c2ecf20Sopenharmony_ci		reg_4 &= ~0x1;
1158c2ecf20Sopenharmony_ci		reg_3 |= (1 << 3);
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_3);
1188c2ecf20Sopenharmony_ci	if (err < 0)
1198c2ecf20Sopenharmony_ci		goto exit;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_4);
1228c2ecf20Sopenharmony_ci	if (err < 0)
1238c2ecf20Sopenharmony_ci		goto exit;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	isl6423->reg_3 = reg_3;
1268c2ecf20Sopenharmony_ci	isl6423->reg_4 = reg_4;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	return 0;
1298c2ecf20Sopenharmony_ciexit:
1308c2ecf20Sopenharmony_ci	dprintk(FE_ERROR, 1, "I/O error <%d>", err);
1318c2ecf20Sopenharmony_ci	return err;
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic int isl6423_set_voltage(struct dvb_frontend *fe,
1368c2ecf20Sopenharmony_ci			       enum fe_sec_voltage voltage)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	struct isl6423_dev *isl6423 = (struct isl6423_dev *) fe->sec_priv;
1398c2ecf20Sopenharmony_ci	u8 reg_3 = isl6423->reg_3;
1408c2ecf20Sopenharmony_ci	u8 reg_4 = isl6423->reg_4;
1418c2ecf20Sopenharmony_ci	int err = 0;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	switch (voltage) {
1448c2ecf20Sopenharmony_ci	case SEC_VOLTAGE_OFF:
1458c2ecf20Sopenharmony_ci		/* EN = 0 */
1468c2ecf20Sopenharmony_ci		reg_4 &= ~(1 << 4);
1478c2ecf20Sopenharmony_ci		break;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	case SEC_VOLTAGE_13:
1508c2ecf20Sopenharmony_ci		/* EN = 1, VSPEN = 1, VTOP = 0, VBOT = 0 */
1518c2ecf20Sopenharmony_ci		reg_4 |= (1 << 4);
1528c2ecf20Sopenharmony_ci		reg_4 &= ~0x3;
1538c2ecf20Sopenharmony_ci		reg_3 |= (1 << 3);
1548c2ecf20Sopenharmony_ci		break;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	case SEC_VOLTAGE_18:
1578c2ecf20Sopenharmony_ci		/* EN = 1, VSPEN = 1, VTOP = 1, VBOT = 0 */
1588c2ecf20Sopenharmony_ci		reg_4 |= (1 << 4);
1598c2ecf20Sopenharmony_ci		reg_4 |=  0x2;
1608c2ecf20Sopenharmony_ci		reg_4 &= ~0x1;
1618c2ecf20Sopenharmony_ci		reg_3 |= (1 << 3);
1628c2ecf20Sopenharmony_ci		break;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	default:
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_3);
1688c2ecf20Sopenharmony_ci	if (err < 0)
1698c2ecf20Sopenharmony_ci		goto exit;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_4);
1728c2ecf20Sopenharmony_ci	if (err < 0)
1738c2ecf20Sopenharmony_ci		goto exit;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	isl6423->reg_3 = reg_3;
1768c2ecf20Sopenharmony_ci	isl6423->reg_4 = reg_4;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	return 0;
1798c2ecf20Sopenharmony_ciexit:
1808c2ecf20Sopenharmony_ci	dprintk(FE_ERROR, 1, "I/O error <%d>", err);
1818c2ecf20Sopenharmony_ci	return err;
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic int isl6423_set_current(struct dvb_frontend *fe)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	struct isl6423_dev *isl6423		= (struct isl6423_dev *) fe->sec_priv;
1878c2ecf20Sopenharmony_ci	u8 reg_3 = isl6423->reg_3;
1888c2ecf20Sopenharmony_ci	const struct isl6423_config *config	= isl6423->config;
1898c2ecf20Sopenharmony_ci	int err = 0;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	switch (config->current_max) {
1928c2ecf20Sopenharmony_ci	case SEC_CURRENT_275m:
1938c2ecf20Sopenharmony_ci		/* 275mA */
1948c2ecf20Sopenharmony_ci		/* ISELH = 0, ISELL = 0 */
1958c2ecf20Sopenharmony_ci		reg_3 &= ~0x3;
1968c2ecf20Sopenharmony_ci		break;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	case SEC_CURRENT_515m:
1998c2ecf20Sopenharmony_ci		/* 515mA */
2008c2ecf20Sopenharmony_ci		/* ISELH = 0, ISELL = 1 */
2018c2ecf20Sopenharmony_ci		reg_3 &= ~0x2;
2028c2ecf20Sopenharmony_ci		reg_3 |=  0x1;
2038c2ecf20Sopenharmony_ci		break;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	case SEC_CURRENT_635m:
2068c2ecf20Sopenharmony_ci		/* 635mA */
2078c2ecf20Sopenharmony_ci		/* ISELH = 1, ISELL = 0 */
2088c2ecf20Sopenharmony_ci		reg_3 &= ~0x1;
2098c2ecf20Sopenharmony_ci		reg_3 |=  0x2;
2108c2ecf20Sopenharmony_ci		break;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	case SEC_CURRENT_800m:
2138c2ecf20Sopenharmony_ci		/* 800mA */
2148c2ecf20Sopenharmony_ci		/* ISELH = 1, ISELL = 1 */
2158c2ecf20Sopenharmony_ci		reg_3 |= 0x3;
2168c2ecf20Sopenharmony_ci		break;
2178c2ecf20Sopenharmony_ci	}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_3);
2208c2ecf20Sopenharmony_ci	if (err < 0)
2218c2ecf20Sopenharmony_ci		goto exit;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	switch (config->curlim) {
2248c2ecf20Sopenharmony_ci	case SEC_CURRENT_LIM_ON:
2258c2ecf20Sopenharmony_ci		/* DCL = 0 */
2268c2ecf20Sopenharmony_ci		reg_3 &= ~0x10;
2278c2ecf20Sopenharmony_ci		break;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	case SEC_CURRENT_LIM_OFF:
2308c2ecf20Sopenharmony_ci		/* DCL = 1 */
2318c2ecf20Sopenharmony_ci		reg_3 |= 0x10;
2328c2ecf20Sopenharmony_ci		break;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	err = isl6423_write(isl6423, reg_3);
2368c2ecf20Sopenharmony_ci	if (err < 0)
2378c2ecf20Sopenharmony_ci		goto exit;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	isl6423->reg_3 = reg_3;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	return 0;
2428c2ecf20Sopenharmony_ciexit:
2438c2ecf20Sopenharmony_ci	dprintk(FE_ERROR, 1, "I/O error <%d>", err);
2448c2ecf20Sopenharmony_ci	return err;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic void isl6423_release(struct dvb_frontend *fe)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	isl6423_set_voltage(fe, SEC_VOLTAGE_OFF);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	kfree(fe->sec_priv);
2528c2ecf20Sopenharmony_ci	fe->sec_priv = NULL;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistruct dvb_frontend *isl6423_attach(struct dvb_frontend *fe,
2568c2ecf20Sopenharmony_ci				    struct i2c_adapter *i2c,
2578c2ecf20Sopenharmony_ci				    const struct isl6423_config *config)
2588c2ecf20Sopenharmony_ci{
2598c2ecf20Sopenharmony_ci	struct isl6423_dev *isl6423;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	isl6423 = kzalloc(sizeof(struct isl6423_dev), GFP_KERNEL);
2628c2ecf20Sopenharmony_ci	if (!isl6423)
2638c2ecf20Sopenharmony_ci		return NULL;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	isl6423->config	= config;
2668c2ecf20Sopenharmony_ci	isl6423->i2c	= i2c;
2678c2ecf20Sopenharmony_ci	fe->sec_priv	= isl6423;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	/* SR3H = 0, SR3M = 1, SR3L = 0 */
2708c2ecf20Sopenharmony_ci	isl6423->reg_3 = 0x02 << 5;
2718c2ecf20Sopenharmony_ci	/* SR4H = 0, SR4M = 1, SR4L = 1 */
2728c2ecf20Sopenharmony_ci	isl6423->reg_4 = 0x03 << 5;
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	if (isl6423_set_current(fe))
2758c2ecf20Sopenharmony_ci		goto exit;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	if (isl6423_set_modulation(fe))
2788c2ecf20Sopenharmony_ci		goto exit;
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	fe->ops.release_sec		= isl6423_release;
2818c2ecf20Sopenharmony_ci	fe->ops.set_voltage		= isl6423_set_voltage;
2828c2ecf20Sopenharmony_ci	fe->ops.enable_high_lnb_voltage = isl6423_voltage_boost;
2838c2ecf20Sopenharmony_ci	isl6423->verbose		= verbose;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return fe;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ciexit:
2888c2ecf20Sopenharmony_ci	kfree(isl6423);
2898c2ecf20Sopenharmony_ci	fe->sec_priv = NULL;
2908c2ecf20Sopenharmony_ci	return NULL;
2918c2ecf20Sopenharmony_ci}
2928c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(isl6423_attach);
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ISL6423 SEC");
2958c2ecf20Sopenharmony_ciMODULE_AUTHOR("Manu Abraham");
2968c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
297