1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | Intersil ISL6423 SEC and LNB Power supply controller |
4 | |
5 | Copyright (C) Manu Abraham <abraham.manu@gmail.com> |
6 | |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/init.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/string.h> |
15 | #include <linux/slab.h> |
16 | |
17 | #include <media/dvb_frontend.h> |
18 | #include "isl6423.h" |
19 | |
20 | static unsigned int verbose; |
21 | module_param(verbose, int, 0644); |
22 | MODULE_PARM_DESC(verbose, "Set Verbosity level" ); |
23 | |
24 | #define FE_ERROR 0 |
25 | #define FE_NOTICE 1 |
26 | #define FE_INFO 2 |
27 | #define FE_DEBUG 3 |
28 | #define FE_DEBUGREG 4 |
29 | |
30 | #define dprintk(__y, __z, format, arg...) do { \ |
31 | if (__z) { \ |
32 | if ((verbose > FE_ERROR) && (verbose > __y)) \ |
33 | printk(KERN_ERR "%s: " format "\n", __func__ , ##arg); \ |
34 | else if ((verbose > FE_NOTICE) && (verbose > __y)) \ |
35 | printk(KERN_NOTICE "%s: " format "\n", __func__ , ##arg); \ |
36 | else if ((verbose > FE_INFO) && (verbose > __y)) \ |
37 | printk(KERN_INFO "%s: " format "\n", __func__ , ##arg); \ |
38 | else if ((verbose > FE_DEBUG) && (verbose > __y)) \ |
39 | printk(KERN_DEBUG "%s: " format "\n", __func__ , ##arg); \ |
40 | } else { \ |
41 | if (verbose > __y) \ |
42 | printk(format, ##arg); \ |
43 | } \ |
44 | } while (0) |
45 | |
46 | struct isl6423_dev { |
47 | const struct isl6423_config *config; |
48 | struct i2c_adapter *i2c; |
49 | |
50 | u8 reg_3; |
51 | u8 reg_4; |
52 | |
53 | unsigned int verbose; |
54 | }; |
55 | |
56 | static int isl6423_write(struct isl6423_dev *isl6423, u8 reg) |
57 | { |
58 | struct i2c_adapter *i2c = isl6423->i2c; |
59 | u8 addr = isl6423->config->addr; |
60 | int err = 0; |
61 | |
62 | struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = ®, .len = 1 }; |
63 | |
64 | dprintk(FE_DEBUG, 1, "write reg %02X" , reg); |
65 | err = i2c_transfer(adap: i2c, msgs: &msg, num: 1); |
66 | if (err < 0) |
67 | goto exit; |
68 | return 0; |
69 | |
70 | exit: |
71 | dprintk(FE_ERROR, 1, "I/O error <%d>" , err); |
72 | return err; |
73 | } |
74 | |
75 | static int isl6423_set_modulation(struct dvb_frontend *fe) |
76 | { |
77 | struct isl6423_dev *isl6423 = (struct isl6423_dev *) fe->sec_priv; |
78 | const struct isl6423_config *config = isl6423->config; |
79 | int err = 0; |
80 | u8 reg_2 = 0; |
81 | |
82 | reg_2 = 0x01 << 5; |
83 | |
84 | if (config->mod_extern) |
85 | reg_2 |= (1 << 3); |
86 | else |
87 | reg_2 |= (1 << 4); |
88 | |
89 | err = isl6423_write(isl6423, reg: reg_2); |
90 | if (err < 0) |
91 | goto exit; |
92 | return 0; |
93 | |
94 | exit: |
95 | dprintk(FE_ERROR, 1, "I/O error <%d>" , err); |
96 | return err; |
97 | } |
98 | |
99 | static int isl6423_voltage_boost(struct dvb_frontend *fe, long arg) |
100 | { |
101 | struct isl6423_dev *isl6423 = (struct isl6423_dev *) fe->sec_priv; |
102 | u8 reg_3 = isl6423->reg_3; |
103 | u8 reg_4 = isl6423->reg_4; |
104 | int err = 0; |
105 | |
106 | if (arg) { |
107 | /* EN = 1, VSPEN = 1, VBOT = 1 */ |
108 | reg_4 |= (1 << 4); |
109 | reg_4 |= 0x1; |
110 | reg_3 |= (1 << 3); |
111 | } else { |
112 | /* EN = 1, VSPEN = 1, VBOT = 0 */ |
113 | reg_4 |= (1 << 4); |
114 | reg_4 &= ~0x1; |
115 | reg_3 |= (1 << 3); |
116 | } |
117 | err = isl6423_write(isl6423, reg: reg_3); |
118 | if (err < 0) |
119 | goto exit; |
120 | |
121 | err = isl6423_write(isl6423, reg: reg_4); |
122 | if (err < 0) |
123 | goto exit; |
124 | |
125 | isl6423->reg_3 = reg_3; |
126 | isl6423->reg_4 = reg_4; |
127 | |
128 | return 0; |
129 | exit: |
130 | dprintk(FE_ERROR, 1, "I/O error <%d>" , err); |
131 | return err; |
132 | } |
133 | |
134 | |
135 | static int isl6423_set_voltage(struct dvb_frontend *fe, |
136 | enum fe_sec_voltage voltage) |
137 | { |
138 | struct isl6423_dev *isl6423 = (struct isl6423_dev *) fe->sec_priv; |
139 | u8 reg_3 = isl6423->reg_3; |
140 | u8 reg_4 = isl6423->reg_4; |
141 | int err = 0; |
142 | |
143 | switch (voltage) { |
144 | case SEC_VOLTAGE_OFF: |
145 | /* EN = 0 */ |
146 | reg_4 &= ~(1 << 4); |
147 | break; |
148 | |
149 | case SEC_VOLTAGE_13: |
150 | /* EN = 1, VSPEN = 1, VTOP = 0, VBOT = 0 */ |
151 | reg_4 |= (1 << 4); |
152 | reg_4 &= ~0x3; |
153 | reg_3 |= (1 << 3); |
154 | break; |
155 | |
156 | case SEC_VOLTAGE_18: |
157 | /* EN = 1, VSPEN = 1, VTOP = 1, VBOT = 0 */ |
158 | reg_4 |= (1 << 4); |
159 | reg_4 |= 0x2; |
160 | reg_4 &= ~0x1; |
161 | reg_3 |= (1 << 3); |
162 | break; |
163 | |
164 | default: |
165 | break; |
166 | } |
167 | err = isl6423_write(isl6423, reg: reg_3); |
168 | if (err < 0) |
169 | goto exit; |
170 | |
171 | err = isl6423_write(isl6423, reg: reg_4); |
172 | if (err < 0) |
173 | goto exit; |
174 | |
175 | isl6423->reg_3 = reg_3; |
176 | isl6423->reg_4 = reg_4; |
177 | |
178 | return 0; |
179 | exit: |
180 | dprintk(FE_ERROR, 1, "I/O error <%d>" , err); |
181 | return err; |
182 | } |
183 | |
184 | static int isl6423_set_current(struct dvb_frontend *fe) |
185 | { |
186 | struct isl6423_dev *isl6423 = (struct isl6423_dev *) fe->sec_priv; |
187 | u8 reg_3 = isl6423->reg_3; |
188 | const struct isl6423_config *config = isl6423->config; |
189 | int err = 0; |
190 | |
191 | switch (config->current_max) { |
192 | case SEC_CURRENT_275m: |
193 | /* 275mA */ |
194 | /* ISELH = 0, ISELL = 0 */ |
195 | reg_3 &= ~0x3; |
196 | break; |
197 | |
198 | case SEC_CURRENT_515m: |
199 | /* 515mA */ |
200 | /* ISELH = 0, ISELL = 1 */ |
201 | reg_3 &= ~0x2; |
202 | reg_3 |= 0x1; |
203 | break; |
204 | |
205 | case SEC_CURRENT_635m: |
206 | /* 635mA */ |
207 | /* ISELH = 1, ISELL = 0 */ |
208 | reg_3 &= ~0x1; |
209 | reg_3 |= 0x2; |
210 | break; |
211 | |
212 | case SEC_CURRENT_800m: |
213 | /* 800mA */ |
214 | /* ISELH = 1, ISELL = 1 */ |
215 | reg_3 |= 0x3; |
216 | break; |
217 | } |
218 | |
219 | err = isl6423_write(isl6423, reg: reg_3); |
220 | if (err < 0) |
221 | goto exit; |
222 | |
223 | switch (config->curlim) { |
224 | case SEC_CURRENT_LIM_ON: |
225 | /* DCL = 0 */ |
226 | reg_3 &= ~0x10; |
227 | break; |
228 | |
229 | case SEC_CURRENT_LIM_OFF: |
230 | /* DCL = 1 */ |
231 | reg_3 |= 0x10; |
232 | break; |
233 | } |
234 | |
235 | err = isl6423_write(isl6423, reg: reg_3); |
236 | if (err < 0) |
237 | goto exit; |
238 | |
239 | isl6423->reg_3 = reg_3; |
240 | |
241 | return 0; |
242 | exit: |
243 | dprintk(FE_ERROR, 1, "I/O error <%d>" , err); |
244 | return err; |
245 | } |
246 | |
247 | static void isl6423_release(struct dvb_frontend *fe) |
248 | { |
249 | isl6423_set_voltage(fe, voltage: SEC_VOLTAGE_OFF); |
250 | |
251 | kfree(objp: fe->sec_priv); |
252 | fe->sec_priv = NULL; |
253 | } |
254 | |
255 | struct dvb_frontend *isl6423_attach(struct dvb_frontend *fe, |
256 | struct i2c_adapter *i2c, |
257 | const struct isl6423_config *config) |
258 | { |
259 | struct isl6423_dev *isl6423; |
260 | |
261 | isl6423 = kzalloc(size: sizeof(struct isl6423_dev), GFP_KERNEL); |
262 | if (!isl6423) |
263 | return NULL; |
264 | |
265 | isl6423->config = config; |
266 | isl6423->i2c = i2c; |
267 | fe->sec_priv = isl6423; |
268 | |
269 | /* SR3H = 0, SR3M = 1, SR3L = 0 */ |
270 | isl6423->reg_3 = 0x02 << 5; |
271 | /* SR4H = 0, SR4M = 1, SR4L = 1 */ |
272 | isl6423->reg_4 = 0x03 << 5; |
273 | |
274 | if (isl6423_set_current(fe)) |
275 | goto exit; |
276 | |
277 | if (isl6423_set_modulation(fe)) |
278 | goto exit; |
279 | |
280 | fe->ops.release_sec = isl6423_release; |
281 | fe->ops.set_voltage = isl6423_set_voltage; |
282 | fe->ops.enable_high_lnb_voltage = isl6423_voltage_boost; |
283 | isl6423->verbose = verbose; |
284 | |
285 | return fe; |
286 | |
287 | exit: |
288 | kfree(objp: isl6423); |
289 | fe->sec_priv = NULL; |
290 | return NULL; |
291 | } |
292 | EXPORT_SYMBOL_GPL(isl6423_attach); |
293 | |
294 | MODULE_DESCRIPTION("ISL6423 SEC" ); |
295 | MODULE_AUTHOR("Manu Abraham" ); |
296 | MODULE_LICENSE("GPL" ); |
297 | |