1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * lnbh25.c
4  *
5  * Driver for LNB supply and control IC LNBH25
6  *
7  * Copyright (C) 2014 NetUP Inc.
8  * Copyright (C) 2014 Sergey Kozlov <serjk@netup.ru>
9  * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru>
10  */
11 
12 #include <linux/module.h>
13 #include <linux/init.h>
14 #include <linux/string.h>
15 #include <linux/slab.h>
16 
17 #include <media/dvb_frontend.h>
18 #include "lnbh25.h"
19 
20 /**
21  * struct lnbh25_priv - LNBH25 driver private data
22  * @i2c:		pointer to the I2C adapter structure
23  * @i2c_address:	I2C address of LNBH25 SEC chip
24  * @config:		Registers configuration:
25  *			offset 0: 1st register address, always 0x02 (DATA1)
26  *			offset 1: DATA1 register value
27  *			offset 2: DATA2 register value
28  */
29 struct lnbh25_priv {
30 	struct i2c_adapter	*i2c;
31 	u8			i2c_address;
32 	u8			config[3];
33 };
34 
35 #define LNBH25_STATUS_OFL	0x1
36 #define LNBH25_STATUS_VMON	0x4
37 #define LNBH25_VSEL_13		0x03
38 #define LNBH25_VSEL_18		0x0a
39 
40 static int lnbh25_read_vmon(struct lnbh25_priv *priv)
41 {
42 	int i, ret;
43 	u8 addr = 0x00;
44 	u8 status[6];
45 	struct i2c_msg msg[2] = {
46 		{
47 			.addr = priv->i2c_address,
48 			.flags = 0,
49 			.len = 1,
50 			.buf = &addr
51 		}, {
52 			.addr = priv->i2c_address,
53 			.flags = I2C_M_RD,
54 			.len = sizeof(status),
55 			.buf = status
56 		}
57 	};
58 
59 	for (i = 0; i < 2; i++) {
60 		ret = i2c_transfer(priv->i2c, &msg[i], 1);
61 		if (ret >= 0 && ret != 1)
62 			ret = -EIO;
63 		if (ret < 0) {
64 			dev_dbg(&priv->i2c->dev,
65 				"%s(): I2C transfer %d failed (%d)\n",
66 				__func__, i, ret);
67 			return ret;
68 		}
69 	}
70 	dev_dbg(&priv->i2c->dev, "%s(): %*ph\n",
71 		__func__, (int) sizeof(status), status);
72 	if ((status[0] & (LNBH25_STATUS_OFL | LNBH25_STATUS_VMON)) != 0) {
73 		dev_err(&priv->i2c->dev,
74 			"%s(): voltage in failure state, status reg 0x%x\n",
75 			__func__, status[0]);
76 		return -EIO;
77 	}
78 	return 0;
79 }
80 
81 static int lnbh25_set_voltage(struct dvb_frontend *fe,
82 			      enum fe_sec_voltage voltage)
83 {
84 	int ret;
85 	u8 data1_reg;
86 	const char *vsel;
87 	struct lnbh25_priv *priv = fe->sec_priv;
88 	struct i2c_msg msg = {
89 		.addr = priv->i2c_address,
90 		.flags = 0,
91 		.len = sizeof(priv->config),
92 		.buf = priv->config
93 	};
94 
95 	switch (voltage) {
96 	case SEC_VOLTAGE_OFF:
97 		data1_reg = 0x00;
98 		vsel = "Off";
99 		break;
100 	case SEC_VOLTAGE_13:
101 		data1_reg = LNBH25_VSEL_13;
102 		vsel = "13V";
103 		break;
104 	case SEC_VOLTAGE_18:
105 		data1_reg = LNBH25_VSEL_18;
106 		vsel = "18V";
107 		break;
108 	default:
109 		return -EINVAL;
110 	}
111 	priv->config[1] = data1_reg;
112 	dev_dbg(&priv->i2c->dev,
113 		"%s(): %s, I2C 0x%x write [ %02x %02x %02x ]\n",
114 		__func__, vsel, priv->i2c_address,
115 		priv->config[0], priv->config[1], priv->config[2]);
116 	ret = i2c_transfer(priv->i2c, &msg, 1);
117 	if (ret >= 0 && ret != 1)
118 		ret = -EIO;
119 	if (ret < 0) {
120 		dev_err(&priv->i2c->dev, "%s(): I2C transfer error (%d)\n",
121 			__func__, ret);
122 		return ret;
123 	}
124 	if (voltage != SEC_VOLTAGE_OFF) {
125 		msleep(120);
126 		ret = lnbh25_read_vmon(priv);
127 	} else {
128 		msleep(20);
129 		ret = 0;
130 	}
131 	return ret;
132 }
133 
134 static void lnbh25_release(struct dvb_frontend *fe)
135 {
136 	struct lnbh25_priv *priv = fe->sec_priv;
137 
138 	dev_dbg(&priv->i2c->dev, "%s()\n", __func__);
139 	lnbh25_set_voltage(fe, SEC_VOLTAGE_OFF);
140 	kfree(fe->sec_priv);
141 	fe->sec_priv = NULL;
142 }
143 
144 struct dvb_frontend *lnbh25_attach(struct dvb_frontend *fe,
145 				   struct lnbh25_config *cfg,
146 				   struct i2c_adapter *i2c)
147 {
148 	struct lnbh25_priv *priv;
149 
150 	dev_dbg(&i2c->dev, "%s()\n", __func__);
151 	priv = kzalloc(sizeof(struct lnbh25_priv), GFP_KERNEL);
152 	if (!priv)
153 		return NULL;
154 	priv->i2c_address = (cfg->i2c_address >> 1);
155 	priv->i2c = i2c;
156 	priv->config[0] = 0x02;
157 	priv->config[1] = 0x00;
158 	priv->config[2] = cfg->data2_config;
159 	fe->sec_priv = priv;
160 	if (lnbh25_set_voltage(fe, SEC_VOLTAGE_OFF)) {
161 		dev_err(&i2c->dev,
162 			"%s(): no LNBH25 found at I2C addr 0x%02x\n",
163 			__func__, priv->i2c_address);
164 		kfree(priv);
165 		fe->sec_priv = NULL;
166 		return NULL;
167 	}
168 
169 	fe->ops.release_sec = lnbh25_release;
170 	fe->ops.set_voltage = lnbh25_set_voltage;
171 
172 	dev_info(&i2c->dev, "%s(): attached at I2C addr 0x%02x\n",
173 		__func__, priv->i2c_address);
174 	return fe;
175 }
176 EXPORT_SYMBOL_GPL(lnbh25_attach);
177 
178 MODULE_DESCRIPTION("ST LNBH25 driver");
179 MODULE_AUTHOR("info@netup.ru");
180 MODULE_LICENSE("GPL");
181