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