1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // Driver for LNB supply and control IC STMicroelectronics LNBH29 4 // 5 // Copyright (c) 2018 Socionext Inc. 6 7 #include <linux/module.h> 8 #include <linux/init.h> 9 #include <linux/slab.h> 10 11 #include <media/dvb_frontend.h> 12 #include "lnbh29.h" 13 14 /** 15 * struct lnbh29_priv - LNBH29 driver private data 16 * @i2c: Pointer to the I2C adapter structure 17 * @i2c_address: I2C address of LNBH29 chip 18 * @config: Registers configuration 19 * offset 0: 1st register address, always 0x01 (DATA) 20 * offset 1: DATA register value 21 */ 22 struct lnbh29_priv { 23 struct i2c_adapter *i2c; 24 u8 i2c_address; 25 u8 config[2]; 26 }; 27 28 #define LNBH29_STATUS_OLF BIT(0) 29 #define LNBH29_STATUS_OTF BIT(1) 30 #define LNBH29_STATUS_VMON BIT(2) 31 #define LNBH29_STATUS_PNG BIT(3) 32 #define LNBH29_STATUS_PDO BIT(4) 33 #define LNBH29_VSEL_MASK GENMASK(2, 0) 34 #define LNBH29_VSEL_0 0x00 35 /* Min: 13.188V, Typ: 13.667V, Max:14V */ 36 #define LNBH29_VSEL_13 0x03 37 /* Min: 18.158V, Typ: 18.817V, Max:19.475V */ 38 #define LNBH29_VSEL_18 0x07 39 40 static int lnbh29_read_vmon(struct lnbh29_priv *priv) 41 { 42 u8 addr = 0x00; 43 u8 status[2]; 44 int ret; 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 ret = i2c_transfer(priv->i2c, msg, 2); 60 if (ret >= 0 && ret != 2) 61 ret = -EIO; 62 if (ret < 0) { 63 dev_dbg(&priv->i2c->dev, "LNBH29 I2C transfer failed (%d)\n", 64 ret); 65 return ret; 66 } 67 68 if (status[0] & (LNBH29_STATUS_OLF | LNBH29_STATUS_VMON)) { 69 dev_err(&priv->i2c->dev, 70 "LNBH29 voltage in failure state, status reg 0x%x\n", 71 status[0]); 72 return -EIO; 73 } 74 75 return 0; 76 } 77 78 static int lnbh29_set_voltage(struct dvb_frontend *fe, 79 enum fe_sec_voltage voltage) 80 { 81 struct lnbh29_priv *priv = fe->sec_priv; 82 u8 data_reg; 83 int ret; 84 struct i2c_msg msg = { 85 .addr = priv->i2c_address, 86 .flags = 0, 87 .len = sizeof(priv->config), 88 .buf = priv->config 89 }; 90 91 switch (voltage) { 92 case SEC_VOLTAGE_OFF: 93 data_reg = LNBH29_VSEL_0; 94 break; 95 case SEC_VOLTAGE_13: 96 data_reg = LNBH29_VSEL_13; 97 break; 98 case SEC_VOLTAGE_18: 99 data_reg = LNBH29_VSEL_18; 100 break; 101 default: 102 return -EINVAL; 103 } 104 priv->config[1] &= ~LNBH29_VSEL_MASK; 105 priv->config[1] |= data_reg; 106 107 ret = i2c_transfer(priv->i2c, &msg, 1); 108 if (ret >= 0 && ret != 1) 109 ret = -EIO; 110 if (ret < 0) { 111 dev_err(&priv->i2c->dev, "LNBH29 I2C transfer error (%d)\n", 112 ret); 113 return ret; 114 } 115 116 /* Soft-start time (Vout 0V to 18V) is Typ. 6ms. */ 117 usleep_range(6000, 20000); 118 119 if (voltage == SEC_VOLTAGE_OFF) 120 return 0; 121 122 return lnbh29_read_vmon(priv); 123 } 124 125 static void lnbh29_release(struct dvb_frontend *fe) 126 { 127 lnbh29_set_voltage(fe, SEC_VOLTAGE_OFF); 128 kfree(fe->sec_priv); 129 fe->sec_priv = NULL; 130 } 131 132 struct dvb_frontend *lnbh29_attach(struct dvb_frontend *fe, 133 struct lnbh29_config *cfg, 134 struct i2c_adapter *i2c) 135 { 136 struct lnbh29_priv *priv; 137 138 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 139 if (!priv) 140 return NULL; 141 142 priv->i2c_address = (cfg->i2c_address >> 1); 143 priv->i2c = i2c; 144 priv->config[0] = 0x01; 145 priv->config[1] = cfg->data_config; 146 fe->sec_priv = priv; 147 148 if (lnbh29_set_voltage(fe, SEC_VOLTAGE_OFF)) { 149 dev_err(&i2c->dev, "no LNBH29 found at I2C addr 0x%02x\n", 150 priv->i2c_address); 151 kfree(priv); 152 fe->sec_priv = NULL; 153 return NULL; 154 } 155 156 fe->ops.release_sec = lnbh29_release; 157 fe->ops.set_voltage = lnbh29_set_voltage; 158 159 dev_info(&i2c->dev, "LNBH29 attached at I2C addr 0x%02x\n", 160 priv->i2c_address); 161 162 return fe; 163 } 164 EXPORT_SYMBOL(lnbh29_attach); 165 166 MODULE_AUTHOR("Katsuhiro Suzuki <suzuki.katsuhiro@socionext.com>"); 167 MODULE_DESCRIPTION("STMicroelectronics LNBH29 driver"); 168 MODULE_LICENSE("GPL v2"); 169