isl6421.c (5204B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * isl6421.h - driver for lnb supply and control ic ISL6421 4 * 5 * Copyright (C) 2006 Andrew de Quincey 6 * Copyright (C) 2006 Oliver Endriss 7 * 8 * the project's page is at https://linuxtv.org 9 */ 10#include <linux/delay.h> 11#include <linux/errno.h> 12#include <linux/init.h> 13#include <linux/kernel.h> 14#include <linux/module.h> 15#include <linux/string.h> 16#include <linux/slab.h> 17 18#include <media/dvb_frontend.h> 19#include "isl6421.h" 20 21struct isl6421 { 22 u8 config; 23 u8 override_or; 24 u8 override_and; 25 struct i2c_adapter *i2c; 26 u8 i2c_addr; 27 bool is_off; 28}; 29 30static int isl6421_set_voltage(struct dvb_frontend *fe, 31 enum fe_sec_voltage voltage) 32{ 33 int ret; 34 u8 buf; 35 bool is_off; 36 struct isl6421 *isl6421 = (struct isl6421 *) fe->sec_priv; 37 struct i2c_msg msg[2] = { 38 { 39 .addr = isl6421->i2c_addr, 40 .flags = 0, 41 .buf = &isl6421->config, 42 .len = 1, 43 }, { 44 .addr = isl6421->i2c_addr, 45 .flags = I2C_M_RD, 46 .buf = &buf, 47 .len = 1, 48 } 49 50 }; 51 52 isl6421->config &= ~(ISL6421_VSEL1 | ISL6421_EN1); 53 54 switch(voltage) { 55 case SEC_VOLTAGE_OFF: 56 is_off = true; 57 break; 58 case SEC_VOLTAGE_13: 59 is_off = false; 60 isl6421->config |= ISL6421_EN1; 61 break; 62 case SEC_VOLTAGE_18: 63 is_off = false; 64 isl6421->config |= (ISL6421_EN1 | ISL6421_VSEL1); 65 break; 66 default: 67 return -EINVAL; 68 } 69 70 /* 71 * If LNBf were not powered on, disable dynamic current limit, as, 72 * according with datasheet, highly capacitive load on the output may 73 * cause a difficult start-up. 74 */ 75 if (isl6421->is_off && !is_off) 76 isl6421->config |= ISL6421_DCL; 77 78 isl6421->config |= isl6421->override_or; 79 isl6421->config &= isl6421->override_and; 80 81 ret = i2c_transfer(isl6421->i2c, msg, 2); 82 if (ret < 0) 83 return ret; 84 if (ret != 2) 85 return -EIO; 86 87 /* Store off status now in case future commands fail */ 88 isl6421->is_off = is_off; 89 90 /* On overflow, the device will try again after 900 ms (typically) */ 91 if (!is_off && (buf & ISL6421_OLF1)) 92 msleep(1000); 93 94 /* Re-enable dynamic current limit */ 95 if ((isl6421->config & ISL6421_DCL) && 96 !(isl6421->override_or & ISL6421_DCL)) { 97 isl6421->config &= ~ISL6421_DCL; 98 99 ret = i2c_transfer(isl6421->i2c, msg, 2); 100 if (ret < 0) 101 return ret; 102 if (ret != 2) 103 return -EIO; 104 } 105 106 /* Check if overload flag is active. If so, disable power */ 107 if (!is_off && (buf & ISL6421_OLF1)) { 108 isl6421->config &= ~(ISL6421_VSEL1 | ISL6421_EN1); 109 ret = i2c_transfer(isl6421->i2c, msg, 1); 110 if (ret < 0) 111 return ret; 112 if (ret != 1) 113 return -EIO; 114 isl6421->is_off = true; 115 116 dev_warn(&isl6421->i2c->dev, 117 "Overload current detected. disabling LNBf power\n"); 118 return -EINVAL; 119 } 120 121 return 0; 122} 123 124static int isl6421_enable_high_lnb_voltage(struct dvb_frontend *fe, long arg) 125{ 126 struct isl6421 *isl6421 = (struct isl6421 *) fe->sec_priv; 127 struct i2c_msg msg = { .addr = isl6421->i2c_addr, .flags = 0, 128 .buf = &isl6421->config, 129 .len = sizeof(isl6421->config) }; 130 131 if (arg) 132 isl6421->config |= ISL6421_LLC1; 133 else 134 isl6421->config &= ~ISL6421_LLC1; 135 136 isl6421->config |= isl6421->override_or; 137 isl6421->config &= isl6421->override_and; 138 139 return (i2c_transfer(isl6421->i2c, &msg, 1) == 1) ? 0 : -EIO; 140} 141 142static int isl6421_set_tone(struct dvb_frontend *fe, 143 enum fe_sec_tone_mode tone) 144{ 145 struct isl6421 *isl6421 = (struct isl6421 *) fe->sec_priv; 146 struct i2c_msg msg = { .addr = isl6421->i2c_addr, .flags = 0, 147 .buf = &isl6421->config, 148 .len = sizeof(isl6421->config) }; 149 150 switch (tone) { 151 case SEC_TONE_ON: 152 isl6421->config |= ISL6421_ENT1; 153 break; 154 case SEC_TONE_OFF: 155 isl6421->config &= ~ISL6421_ENT1; 156 break; 157 default: 158 return -EINVAL; 159 } 160 161 isl6421->config |= isl6421->override_or; 162 isl6421->config &= isl6421->override_and; 163 164 return (i2c_transfer(isl6421->i2c, &msg, 1) == 1) ? 0 : -EIO; 165} 166 167static void isl6421_release(struct dvb_frontend *fe) 168{ 169 /* power off */ 170 isl6421_set_voltage(fe, SEC_VOLTAGE_OFF); 171 172 /* free */ 173 kfree(fe->sec_priv); 174 fe->sec_priv = NULL; 175} 176 177struct dvb_frontend *isl6421_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, u8 i2c_addr, 178 u8 override_set, u8 override_clear, bool override_tone) 179{ 180 struct isl6421 *isl6421 = kmalloc(sizeof(struct isl6421), GFP_KERNEL); 181 if (!isl6421) 182 return NULL; 183 184 /* default configuration */ 185 isl6421->config = ISL6421_ISEL1; 186 isl6421->i2c = i2c; 187 isl6421->i2c_addr = i2c_addr; 188 fe->sec_priv = isl6421; 189 190 /* bits which should be forced to '1' */ 191 isl6421->override_or = override_set; 192 193 /* bits which should be forced to '0' */ 194 isl6421->override_and = ~override_clear; 195 196 /* detect if it is present or not */ 197 if (isl6421_set_voltage(fe, SEC_VOLTAGE_OFF)) { 198 kfree(isl6421); 199 fe->sec_priv = NULL; 200 return NULL; 201 } 202 203 isl6421->is_off = true; 204 205 /* install release callback */ 206 fe->ops.release_sec = isl6421_release; 207 208 /* override frontend ops */ 209 fe->ops.set_voltage = isl6421_set_voltage; 210 fe->ops.enable_high_lnb_voltage = isl6421_enable_high_lnb_voltage; 211 if (override_tone) 212 fe->ops.set_tone = isl6421_set_tone; 213 214 return fe; 215} 216EXPORT_SYMBOL(isl6421_attach); 217 218MODULE_DESCRIPTION("Driver for lnb supply and control ic isl6421"); 219MODULE_AUTHOR("Andrew de Quincey & Oliver Endriss"); 220MODULE_LICENSE("GPL");