diff options
Diffstat (limited to 'drivers/media/dvb-frontends/a8293.c')
-rw-r--r-- | drivers/media/dvb-frontends/a8293.c | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/drivers/media/dvb-frontends/a8293.c b/drivers/media/dvb-frontends/a8293.c new file mode 100644 index 0000000000..f39887c049 --- /dev/null +++ b/drivers/media/dvb-frontends/a8293.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Allegro A8293 SEC driver + * + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + */ + +#include "a8293.h" + +#define A8293_FLAG_ODT 0x10 + +struct a8293_dev { + struct i2c_client *client; + u8 reg[2]; + int volt_slew_nanos_per_mv; +}; + +/* + * When increasing voltage, do so in minimal steps over time, minimizing + * risk of vIN undervoltage. + */ + +static int a8293_set_voltage_slew(struct a8293_dev *dev, + struct i2c_client *client, + enum fe_sec_voltage fe_sec_voltage, + int min_nanos_per_mv) +{ + int ret; + u8 reg0, reg1; + int new_volt_idx; + const int idx_to_mv[] = { + 0, 12709, 13042, 13375, 14042, 15042, 18042, 18709, 19042 + }; + const u8 idx_to_reg[] = { + 0x00, 0x20, 0x21, 0x22, 0x24, 0x27, 0x28, 0x2A, 0x2B + }; + int this_volt_idx; + u8 status; + int prev_volt_idx; + + dev_dbg(&client->dev, "set_voltage_slew fe_sec_voltage=%d\n", + fe_sec_voltage); + + /* Read status register to clear any stale faults. */ + ret = i2c_master_recv(client, &status, 1); + if (ret < 0) + goto err; + + /* Determine previous voltage */ + switch (dev->reg[0] & 0x2F) { + case 0x00: + prev_volt_idx = 0; + break; + case 0x20: + prev_volt_idx = 1; + break; + case 0x21: + prev_volt_idx = 2; + break; + case 0x22: + prev_volt_idx = 3; + break; + case 0x24: + prev_volt_idx = 4; + break; + case 0x27: + prev_volt_idx = 5; + break; + case 0x28: + prev_volt_idx = 6; + break; + case 0x2A: + prev_volt_idx = 7; + break; + case 0x2B: + prev_volt_idx = 8; + break; + default: + prev_volt_idx = 0; + } + + /* Determine new voltage */ + switch (fe_sec_voltage) { + case SEC_VOLTAGE_OFF: + new_volt_idx = 0; + break; + case SEC_VOLTAGE_13: + new_volt_idx = 2; + break; + case SEC_VOLTAGE_18: + new_volt_idx = 6; + break; + default: + ret = -EINVAL; + goto err; + } + + /* Slew to new voltage if new voltage is greater than current voltage */ + this_volt_idx = prev_volt_idx; + if (this_volt_idx < new_volt_idx) { + while (this_volt_idx < new_volt_idx) { + int delta_mv = idx_to_mv[this_volt_idx+1] - idx_to_mv[this_volt_idx]; + int min_wait_time = delta_mv * min_nanos_per_mv; + + reg0 = idx_to_reg[this_volt_idx+1]; + reg0 |= A8293_FLAG_ODT; + + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + this_volt_idx++; + usleep_range(min_wait_time, min_wait_time * 2); + } + } else { /* Else just set the voltage */ + reg0 = idx_to_reg[new_volt_idx]; + reg0 |= A8293_FLAG_ODT; + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + } + + /* TMODE=0, TGATE=1 */ + reg1 = 0x82; + if (reg1 != dev->reg[1]) { + ret = i2c_master_send(client, ®1, 1); + if (ret < 0) + goto err; + dev->reg[1] = reg1; + } + + usleep_range(1500, 5000); + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + + +static int a8293_set_voltage_noslew(struct dvb_frontend *fe, + enum fe_sec_voltage fe_sec_voltage) +{ + struct a8293_dev *dev = fe->sec_priv; + struct i2c_client *client = dev->client; + int ret; + u8 reg0, reg1; + + dev_dbg(&client->dev, "set_voltage_noslew fe_sec_voltage=%d\n", + fe_sec_voltage); + + switch (fe_sec_voltage) { + case SEC_VOLTAGE_OFF: + /* ENB=0 */ + reg0 = 0x10; + break; + case SEC_VOLTAGE_13: + /* VSEL0=1, VSEL1=0, VSEL2=0, VSEL3=0, ENB=1*/ + reg0 = 0x31; + break; + case SEC_VOLTAGE_18: + /* VSEL0=0, VSEL1=0, VSEL2=0, VSEL3=1, ENB=1*/ + reg0 = 0x38; + break; + default: + ret = -EINVAL; + goto err; + } + if (reg0 != dev->reg[0]) { + ret = i2c_master_send(client, ®0, 1); + if (ret < 0) + goto err; + dev->reg[0] = reg0; + } + + /* TMODE=0, TGATE=1 */ + reg1 = 0x82; + if (reg1 != dev->reg[1]) { + ret = i2c_master_send(client, ®1, 1); + if (ret < 0) + goto err; + dev->reg[1] = reg1; + } + + usleep_range(1500, 50000); + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int a8293_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage fe_sec_voltage) +{ + struct a8293_dev *dev = fe->sec_priv; + struct i2c_client *client = dev->client; + int volt_slew_nanos_per_mv = dev->volt_slew_nanos_per_mv; + + dev_dbg(&client->dev, "set_voltage volt_slew_nanos_per_mv=%d\n", + volt_slew_nanos_per_mv); + + /* Use slew version if slew rate is set to a sane value */ + if (volt_slew_nanos_per_mv > 0 && volt_slew_nanos_per_mv < 1600) + a8293_set_voltage_slew(dev, client, fe_sec_voltage, + volt_slew_nanos_per_mv); + else + a8293_set_voltage_noslew(fe, fe_sec_voltage); + + return 0; +} + +static int a8293_probe(struct i2c_client *client) +{ + struct a8293_dev *dev; + struct a8293_platform_data *pdata = client->dev.platform_data; + struct dvb_frontend *fe = pdata->dvb_frontend; + int ret; + u8 buf[2]; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + dev->client = client; + dev->volt_slew_nanos_per_mv = pdata->volt_slew_nanos_per_mv; + + /* check if the SEC is there */ + ret = i2c_master_recv(client, buf, 2); + if (ret < 0) + goto err_kfree; + + /* override frontend ops */ + fe->ops.set_voltage = a8293_set_voltage; + fe->sec_priv = dev; + i2c_set_clientdata(client, dev); + + dev_info(&client->dev, "Allegro A8293 SEC successfully attached\n"); + return 0; +err_kfree: + kfree(dev); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static void a8293_remove(struct i2c_client *client) +{ + struct a8293_dev *dev = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "\n"); + + kfree(dev); +} + +static const struct i2c_device_id a8293_id_table[] = { + {"a8293", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, a8293_id_table); + +static struct i2c_driver a8293_driver = { + .driver = { + .name = "a8293", + .suppress_bind_attrs = true, + }, + .probe = a8293_probe, + .remove = a8293_remove, + .id_table = a8293_id_table, +}; + +module_i2c_driver(a8293_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Allegro A8293 SEC driver"); +MODULE_LICENSE("GPL"); |