forked from Minki/linux
d4ef930638
Chips with 16-bit registers don't usually work well with I2C block commands. For example, neither the LM75 datasheet nor the TMP102 datasheet mentions block command support, and in fact it does not work for any of those chips. Also, it is not clear how the block command would handle 16-bit SMBus operations in the fist place, since the data format associated with those commands is either little endian or big endian, which requires some kind of conversion to or from host byte order. Only use i2c block commands if both register and value width is 8 bit. Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Mark Brown <broonie@kernel.org>
316 lines
7.3 KiB
C
316 lines
7.3 KiB
C
/*
|
|
* Register map access API - I2C support
|
|
*
|
|
* Copyright 2011 Wolfson Microelectronics plc
|
|
*
|
|
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/regmap.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/module.h>
|
|
|
|
#include "internal.h"
|
|
|
|
static int regmap_smbus_byte_reg_read(void *context, unsigned int reg,
|
|
unsigned int *val)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
int ret;
|
|
|
|
if (reg > 0xff)
|
|
return -EINVAL;
|
|
|
|
ret = i2c_smbus_read_byte_data(i2c, reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*val = ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int regmap_smbus_byte_reg_write(void *context, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
|
|
if (val > 0xff || reg > 0xff)
|
|
return -EINVAL;
|
|
|
|
return i2c_smbus_write_byte_data(i2c, reg, val);
|
|
}
|
|
|
|
static struct regmap_bus regmap_smbus_byte = {
|
|
.reg_write = regmap_smbus_byte_reg_write,
|
|
.reg_read = regmap_smbus_byte_reg_read,
|
|
};
|
|
|
|
static int regmap_smbus_word_reg_read(void *context, unsigned int reg,
|
|
unsigned int *val)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
int ret;
|
|
|
|
if (reg > 0xff)
|
|
return -EINVAL;
|
|
|
|
ret = i2c_smbus_read_word_data(i2c, reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*val = ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int regmap_smbus_word_reg_write(void *context, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
|
|
if (val > 0xffff || reg > 0xff)
|
|
return -EINVAL;
|
|
|
|
return i2c_smbus_write_word_data(i2c, reg, val);
|
|
}
|
|
|
|
static struct regmap_bus regmap_smbus_word = {
|
|
.reg_write = regmap_smbus_word_reg_write,
|
|
.reg_read = regmap_smbus_word_reg_read,
|
|
};
|
|
|
|
static int regmap_smbus_word_read_swapped(void *context, unsigned int reg,
|
|
unsigned int *val)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
int ret;
|
|
|
|
if (reg > 0xff)
|
|
return -EINVAL;
|
|
|
|
ret = i2c_smbus_read_word_swapped(i2c, reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*val = ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int regmap_smbus_word_write_swapped(void *context, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
|
|
if (val > 0xffff || reg > 0xff)
|
|
return -EINVAL;
|
|
|
|
return i2c_smbus_write_word_swapped(i2c, reg, val);
|
|
}
|
|
|
|
static struct regmap_bus regmap_smbus_word_swapped = {
|
|
.reg_write = regmap_smbus_word_write_swapped,
|
|
.reg_read = regmap_smbus_word_read_swapped,
|
|
};
|
|
|
|
static int regmap_i2c_write(void *context, const void *data, size_t count)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
int ret;
|
|
|
|
ret = i2c_master_send(i2c, data, count);
|
|
if (ret == count)
|
|
return 0;
|
|
else if (ret < 0)
|
|
return ret;
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
static int regmap_i2c_gather_write(void *context,
|
|
const void *reg, size_t reg_size,
|
|
const void *val, size_t val_size)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
struct i2c_msg xfer[2];
|
|
int ret;
|
|
|
|
/* If the I2C controller can't do a gather tell the core, it
|
|
* will substitute in a linear write for us.
|
|
*/
|
|
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_NOSTART))
|
|
return -ENOTSUPP;
|
|
|
|
xfer[0].addr = i2c->addr;
|
|
xfer[0].flags = 0;
|
|
xfer[0].len = reg_size;
|
|
xfer[0].buf = (void *)reg;
|
|
|
|
xfer[1].addr = i2c->addr;
|
|
xfer[1].flags = I2C_M_NOSTART;
|
|
xfer[1].len = val_size;
|
|
xfer[1].buf = (void *)val;
|
|
|
|
ret = i2c_transfer(i2c->adapter, xfer, 2);
|
|
if (ret == 2)
|
|
return 0;
|
|
if (ret < 0)
|
|
return ret;
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
static int regmap_i2c_read(void *context,
|
|
const void *reg, size_t reg_size,
|
|
void *val, size_t val_size)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
struct i2c_msg xfer[2];
|
|
int ret;
|
|
|
|
xfer[0].addr = i2c->addr;
|
|
xfer[0].flags = 0;
|
|
xfer[0].len = reg_size;
|
|
xfer[0].buf = (void *)reg;
|
|
|
|
xfer[1].addr = i2c->addr;
|
|
xfer[1].flags = I2C_M_RD;
|
|
xfer[1].len = val_size;
|
|
xfer[1].buf = val;
|
|
|
|
ret = i2c_transfer(i2c->adapter, xfer, 2);
|
|
if (ret == 2)
|
|
return 0;
|
|
else if (ret < 0)
|
|
return ret;
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
static struct regmap_bus regmap_i2c = {
|
|
.write = regmap_i2c_write,
|
|
.gather_write = regmap_i2c_gather_write,
|
|
.read = regmap_i2c_read,
|
|
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
|
|
.val_format_endian_default = REGMAP_ENDIAN_BIG,
|
|
};
|
|
|
|
static int regmap_i2c_smbus_i2c_write(void *context, const void *data,
|
|
size_t count)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
|
|
if (count < 1)
|
|
return -EINVAL;
|
|
if (count >= I2C_SMBUS_BLOCK_MAX)
|
|
return -E2BIG;
|
|
|
|
--count;
|
|
return i2c_smbus_write_i2c_block_data(i2c, ((u8 *)data)[0], count,
|
|
((u8 *)data + 1));
|
|
}
|
|
|
|
static int regmap_i2c_smbus_i2c_read(void *context, const void *reg,
|
|
size_t reg_size, void *val,
|
|
size_t val_size)
|
|
{
|
|
struct device *dev = context;
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
int ret;
|
|
|
|
if (reg_size != 1 || val_size < 1)
|
|
return -EINVAL;
|
|
if (val_size >= I2C_SMBUS_BLOCK_MAX)
|
|
return -E2BIG;
|
|
|
|
ret = i2c_smbus_read_i2c_block_data(i2c, ((u8 *)reg)[0], val_size, val);
|
|
if (ret == val_size)
|
|
return 0;
|
|
else if (ret < 0)
|
|
return ret;
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
static struct regmap_bus regmap_i2c_smbus_i2c_block = {
|
|
.write = regmap_i2c_smbus_i2c_write,
|
|
.read = regmap_i2c_smbus_i2c_read,
|
|
.max_raw_read = I2C_SMBUS_BLOCK_MAX,
|
|
.max_raw_write = I2C_SMBUS_BLOCK_MAX,
|
|
};
|
|
|
|
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
|
|
const struct regmap_config *config)
|
|
{
|
|
if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
|
|
return ®map_i2c;
|
|
else if (config->val_bits == 8 && config->reg_bits == 8 &&
|
|
i2c_check_functionality(i2c->adapter,
|
|
I2C_FUNC_SMBUS_I2C_BLOCK))
|
|
return ®map_i2c_smbus_i2c_block;
|
|
else if (config->val_bits == 16 && config->reg_bits == 8 &&
|
|
i2c_check_functionality(i2c->adapter,
|
|
I2C_FUNC_SMBUS_WORD_DATA))
|
|
switch (regmap_get_val_endian(&i2c->dev, NULL, config)) {
|
|
case REGMAP_ENDIAN_LITTLE:
|
|
return ®map_smbus_word;
|
|
case REGMAP_ENDIAN_BIG:
|
|
return ®map_smbus_word_swapped;
|
|
default: /* everything else is not supported */
|
|
break;
|
|
}
|
|
else if (config->val_bits == 8 && config->reg_bits == 8 &&
|
|
i2c_check_functionality(i2c->adapter,
|
|
I2C_FUNC_SMBUS_BYTE_DATA))
|
|
return ®map_smbus_byte;
|
|
|
|
return ERR_PTR(-ENOTSUPP);
|
|
}
|
|
|
|
struct regmap *__regmap_init_i2c(struct i2c_client *i2c,
|
|
const struct regmap_config *config,
|
|
struct lock_class_key *lock_key,
|
|
const char *lock_name)
|
|
{
|
|
const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);
|
|
|
|
if (IS_ERR(bus))
|
|
return ERR_CAST(bus);
|
|
|
|
return __regmap_init(&i2c->dev, bus, &i2c->dev, config,
|
|
lock_key, lock_name);
|
|
}
|
|
EXPORT_SYMBOL_GPL(__regmap_init_i2c);
|
|
|
|
struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c,
|
|
const struct regmap_config *config,
|
|
struct lock_class_key *lock_key,
|
|
const char *lock_name)
|
|
{
|
|
const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);
|
|
|
|
if (IS_ERR(bus))
|
|
return ERR_CAST(bus);
|
|
|
|
return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config,
|
|
lock_key, lock_name);
|
|
}
|
|
EXPORT_SYMBOL_GPL(__devm_regmap_init_i2c);
|
|
|
|
MODULE_LICENSE("GPL");
|