diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 642c42b1fae7..b043c00f8939 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1801,8 +1801,8 @@ config TI_ADS1119 config TI_ADS112C14 tristate "Texas Instruments ADS112C14/ADS122C14" depends on I2C + select CRC8 select REGMAP - select REGMAP_I2C help If you say yes here you get support for Texas Instruments ADS112C14, ADS122C14 ADC chips. diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 3035d45b64b0..5610d16f4467 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -156,6 +157,9 @@ static const u32 ads112c14_pga_gains_x10[] = { 200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */ }; +#define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07 +DECLARE_CRC8_TABLE(ads112c14_crc8_table); + struct ads112c14_chip_info { const char *name; u8 device_id; @@ -241,6 +245,7 @@ struct ads112c14_data { struct regmap *regmap; /* Synchronizes access to register value fields. */ struct mutex lock; + bool i2c_crc_enabled; u32 avdd_uV; u32 ext_ref_uV; bool refp_is_avdd; @@ -289,6 +294,120 @@ static const struct reg_default ads112c14_reg_defaults[] = { { ADS112C14_REG_IDAC_MUX_CFG, FIELD_PREP_CONST(ADS112C14_IDAC_MUX_CFG_I2MUX, 1) }, }; +/** + * ads112c14_i2c_read_bytes() - Read bytes from the device over I2C + * @client: I2C client for the device + * @cmd: Command to send to the device before reading + * @buf: Buffer to store the read bytes + * @len: Number of bytes to read + * @use_crc: Whether to use CRC8 for data integrity check + * + * If I2C_CRC is enabled, @use_crc may be set to true to perform a CRC8 check + * on the received data. + */ +static int ads112c14_i2c_read_bytes(struct i2c_client *client, u8 cmd, + u8 *buf, u8 len, bool use_crc) +{ + u8 rx_buf[4]; /* Up to 3 data bytes + 1 CRC byte. */ + u8 rx_len; + int ret; + + rx_len = len + (use_crc ? 1 : 0); + + if (rx_len > sizeof(rx_buf)) + return -EINVAL; + + ret = i2c_smbus_read_i2c_block_data(client, cmd, rx_len, rx_buf); + if (ret < 0) + return ret; + + if (use_crc) { + u8 crc = crc8(ads112c14_crc8_table, rx_buf, len, CRC8_INIT_VALUE); + + if (crc != rx_buf[len]) + return -EBADMSG; + } + + memcpy(buf, rx_buf, len); + + return 0; +} + +/** + * ads112c14_regmap_bus_read() - Read a register from the device + * @context: Pointer to the device context + * @reg_buf: Register address to read + * @reg_size: Size of the register address (should be 1) + * @val_buf: Buffer to store the read value + * @val_size: Size of the value to read + * + * Custom regmap read function that also does CRC check when enabled. + */ +static int ads112c14_regmap_bus_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct ads112c14_data *data = context; + struct device *dev = regmap_get_device(data->regmap); + struct i2c_client *client = to_i2c_client(dev); + const u8 *cmd = reg_buf; + + if (reg_size != 1) + return -EINVAL; + + return ads112c14_i2c_read_bytes(client, cmd[0], val_buf, val_size, + data->i2c_crc_enabled); +} + +/** + * ads112c14_regmap_bus_write() - Write a register to the device + * @context: Pointer to the device context + * @data_buf: Buffer containing the register address and value to write + * @count: Number of bytes to write + * + * Custom regmap write function that also does readback with CRC check of + * nonvolatile registers when CRC is enabled. + */ +static int ads112c14_regmap_bus_write(void *context, const void *data_buf, + size_t count) +{ + struct ads112c14_data *data = context; + struct device *dev = regmap_get_device(data->regmap); + struct i2c_client *client = to_i2c_client(dev); + const u8 *tx = data_buf; + u8 reg, readback; + int ret; + + if (count != 2) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(client, tx[0], tx[1]); + if (ret) + return ret; + + reg = tx[0] & ~ADS112C14_CMD_WREG; + + if (!data->i2c_crc_enabled || ads112c14_volatile_reg(dev, reg)) + return 0; + + ret = ads112c14_i2c_read_bytes(client, reg | ADS112C14_CMD_RREG, + &readback, sizeof(readback), true); + if (ret) + return ret; + + if (readback != tx[1]) + return -EIO; + + return 0; +} + +static const struct regmap_bus ads112c14_regmap_bus = { + .read = ads112c14_regmap_bus_read, + .write = ads112c14_regmap_bus_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + static const struct regmap_config ads112c14_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -486,13 +605,9 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, if (ret) return ret; - ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, - BITS_TO_BYTES(data->chip_info->resolution_bits), - buf); - if (ret < 0) - return ret; - - return 0; + return ads112c14_i2c_read_bytes(client, ADS112C14_CMD_RDATA, buf, + BITS_TO_BYTES(data->chip_info->resolution_bits), + data->i2c_crc_enabled); } static int ads112c14_read_raw(struct iio_dev *indio_dev, @@ -1121,7 +1236,8 @@ static int ads112c14_probe(struct i2c_client *client) /* It takes some time for the internal reference to stabilize. */ fsleep(10 * USEC_PER_MSEC); - data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config); + data->regmap = devm_regmap_init(dev, &ads112c14_regmap_bus, data, + &ads112c14_regmap_config); if (IS_ERR(data->regmap)) return dev_err_probe(dev, PTR_ERR(data->regmap), "failed to init regmap\n"); @@ -1158,6 +1274,13 @@ static int ads112c14_probe(struct i2c_client *client) if (ret) return ret; + ret = regmap_set_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG, + ADS112C14_DIGITAL_CFG_I2C_CRC_EN); + if (ret) + return ret; + + data->i2c_crc_enabled = true; + ret = regmap_read(data->regmap, ADS112C14_REG_DEVICE_ID, ®_val); if (ret) return ret; @@ -1209,6 +1332,13 @@ static const struct i2c_device_id ads112c14_id[] = { }; MODULE_DEVICE_TABLE(i2c, ads112c14_id); +static int ads112c14_i2c_add_driver(struct i2c_driver *driver) +{ + crc8_populate_msb(ads112c14_crc8_table, ADS112C14_I2C_CRC8_POLYNOMIAL); + + return i2c_add_driver(driver); +} + static struct i2c_driver ads112c14_driver = { .driver = { .name = "ads112c14", @@ -1217,7 +1347,7 @@ static struct i2c_driver ads112c14_driver = { .probe = ads112c14_probe, .id_table = ads112c14_id, }; -module_i2c_driver(ads112c14_driver); +module_driver(ads112c14_driver, ads112c14_i2c_add_driver, i2c_del_driver); MODULE_AUTHOR("David Lechner (TI) "); MODULE_DESCRIPTION("TI ADS112C14 I2C ADC driver");