目录
读写实质: 最终就是通过client中的adapter->algo读写
基于zynqmp iic控制器
i2c模块框图 ug-1085(ch22)
(a)有两条i2c总线 i2c0 i2c1中断id 49 50; CPU通过APB总线访问i2c控制器 ,i2c控制器通过scl和sda链接外部设备;
(b)中断id: ug-1085(13)中断控制器是 GIC
(c)寄存器和数据流向框图
Cpu通过APB总线访问i2c总线上挂载的外设,通过四个寄存器(控制寄存器、 发送数据寄存器、接收数据寄存器、状态寄存器)控制i2c控制器和外设(从设备)通信;
这些寄存器对应的地址(ug1087)
上述这些寄存器一般都在控制器发生传输初始化时候使用,后面控制器驱动程序中会用到;
2、控制器和从设备的设备树
pd_i2c1: pd-i2c1 {
#power-domain-cells = <0x0>;
pd-id = <0x26>;
};
i2c1: i2c@ff030000 {
compatible = "cdns,i2c-r1p14", "cdns,i2c-r1p10"; /* 匹配控制器驱动 */
status = "okay"; /* 表示本节点是否生效 */
clock-frequency = <100000>; /* scl时钟*/
interrupt-parent = <&gic>; /*父中断是gic中断控制器*/
interrupts = <0 18 4>; /* 中断号 18 高电平*/
reg = <0x0 0xff030000 0x0 0x1000>; /* i2c基地址 */
#address-cells = <1>;
#size-cells = <0>;
power-domains = <&pd_i2c1>;
tmp401: tmp401@4c { /* u23 */
compatible = "ti,tmp401";
reg = <0x4c>;
};
};
根据设备树搜索源码: grep “cdns,i2c-r1p” -r | grep -v “dts”
3、查看i2c控制器驱动注册流程: 标准的平台驱动框架
匹配后执行cdns_i2c_probe函数;
probe中需要做的几件事:
- 获取设备树资源并使用这些资源初始化i2c寄存器(控制、发送、接收、状态、中断);
- 构建一个i2c_adapter,并初始化必须实现成员algo的方法
- id->adap.algo = &cdns_i2c_algo;
- master_xfer 和functionality
- 初始化时钟注册中断(因为中断用来收发数据);
- devm_request_irq(&pdev->dev, id->irq, cdns_i2c_isr, 0,DRIVER_NAME, id);
- 初始化并向总线注册一个适配器; i2c_add_adapter(&id->adap);
分析probe函数:
static int cdns_i2c_probe(struct platform_device *pdev)
{
struct resource *r_mem;
struct cdns_i2c *id;
int ret;
const struct of_device_id *match;
/* 给struct cdns_i2c 申请空间 内部包含适配器等其他资源 */
id = devm_kzalloc(&pdev->dev, sizeof(*id), GFP_KERNEL);
id->dev = &pdev->dev; /* 保存平台设备 */
platform_set_drvdata(pdev, id); /* 保存struct cdns_i2c到驱动数据 */
match = of_match_node(cdns_i2c_of_match, pdev->dev.of_node);
if (match && match->data) {
const struct cdns_platform_data *data = match->data;
id->quirks = data->quirks; // 0
}
id->pinctrl = devm_pinctrl_get(&pdev->dev);
if (!IS_ERR(id->pinctrl)) {
ret = cdns_i2c_init_recovery_info(id, pdev);
if (ret)
return ret;
}
/* 从设备树获取基地址 */
r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
id->membase = devm_ioremap_resource(&pdev->dev, r_mem);
if (IS_ERR(id->membase))
return PTR_ERR(id->membase);
/* 获取中断号 */
id->irq = platform_get_irq(pdev, 0);
/* 初始化适配器 */
id->adap.owner = THIS_MODULE;
id->adap.dev.of_node = pdev->dev.of_node;
id->adap.algo = &cdns_i2c_algo;
id->adap.timeout = CDNS_I2C_TIMEOUT;
id->adap.retries = 3; /* Default retry value. */
id->adap.algo_data = id;
id->adap.dev.parent = &pdev->dev;
init_completion(&id->xfer_done);
snprintf(id->adap.name, sizeof(id->adap.name),
"Cadence I2C at %08lx", (unsigned long)r_mem->start);
id->clk = devm_clk_get(&pdev->dev, NULL);
ret = clk_prepare_enable(id->clk);
pm_runtime_set_autosuspend_delay(id->dev, CNDS_I2C_PM_TIMEOUT);
pm_runtime_use_autosuspend(id->dev);
pm_runtime_set_active(id->dev);
pm_runtime_enable(id->dev);
id->clk_rate_change_nb.notifier_call = cdns_i2c_clk_notifier_cb;
ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&id->i2c_clk);
if (ret || (id->i2c_clk > CDNS_I2C_SPEED_MAX))
id->i2c_clk = CDNS_I2C_SPEED_DEFAULT;
/* 是指控制寄存器状态 使能ack 7位传输 master模式 */
id->ctrl_reg = CDNS_I2C_CR_ACK_EN | CDNS_I2C_CR_NEA | CDNS_I2C_CR_MS;
/* 设置时钟 */
ret = cdns_i2c_setclk(id->input_clk, id);
/* 注册中断 */
ret = devm_request_irq(&pdev->dev, id->irq, cdns_i2c_isr, 0,DRIVER_NAME, id);
/* 设置超时时间 把id->ctrl_reg 写入寄存器 */
cdns_i2c_init(id);
/* 注册适配器 */
ret = i2c_add_adapter(&id->adap);
if (ret < 0)
goto err_clk_dis;
dev_info(&pdev->dev, "%u kHz mmio %08lx irq %d\n",
id->i2c_clk / 1000, (unsigned long)r_mem->start, id->irq);
return 0;
}
主要看下cdns_i2c_algo 和中断处理函数 cdns_i2c_isr:
static const struct i2c_algorithm cdns_i2c_algo = {
.master_xfer = cdns_i2c_master_xfer, //i2c-core-base.c中__i2c_transfer会调用
.functionality = cdns_i2c_func, //是能够master_xfer接口能够支持的iic驱动种类
};
master_xfer都是在读写接口中调用,被核心层封装在了__i2c_transfer中如下:
zynqmp中cdns_i2c_master_xfer的实现:
这个函数中就可以看到对寄存器的一些操作,控制寄存器 状态寄存器等
static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){
int ret, count;
u32 reg;
struct cdns_i2c *id = adap->algo_data;
bool hold_quirk;
ret = pm_runtime_get_sync(id->dev);
/* CDNS_I2C_SR_OFFSET = 0x04; 读取0x4状态寄存器 CDNS_I2C_SR_BA: bit 8 总线正在传输中 */
if (msgs->len)
if (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) & CDNS_I2C_SR_BA) { //总线正在传输中的话直接返回
ret = -EAGAIN;
goto out;
}
hold_quirk = !!(id->quirks & CDNS_I2C_BROKEN_HOLD_BIT); //重复标志
/* Set the flag to one when multiple messages are to be * processed with a repeated start. */
if (num > 1) { //发送多条消息分支
for (count = 0; (count < num - 1 && hold_quirk); count++) {
if (msgs[count].flags & I2C_M_RD) {
dev_warn(adap->dev.parent,
"Can't do repeated start after a receive message\n");
ret = -EOPNOTSUPP;
goto out;
}
}
id->bus_hold_flag = 1;
reg = cdns_i2c_readreg(CDNS_I2C_CR_OFFSET);
reg |= CDNS_I2C_CR_HOLD;
cdns_i2c_writereg(reg, CDNS_I2C_CR_OFFSET); // 控制寄存器置位bit 4
} else { //发送单个
id->bus_hold_flag = 0;
}
/* Process the msg one by one */
for (count = 0; count < num; count++, msgs++) {
if (count == (num - 1)) //当最后一次发送的时候释放标志位;释放sclk
id->bus_hold_flag = 0;
ret = cdns_i2c_process_msg(id, msgs, adap); //发送消息
/* Report the other error interrupts to application */
if (id->err_status) {
cdns_i2c_master_reset(adap); //如果发送消息过程中出现了错误则复位,后面看如何复位
if (id->err_status & CDNS_I2C_IXR_NACK) {
ret = -ENXIO;
goto out;
}
ret = -EIO;
goto out;
}
}
ret = num;
out:
pm_runtime_mark_last_busy(id->dev);
pm_runtime_put_autosuspend(id->dev);
return ret;
}
循环当中调用的就是cdns_i2c_process_msg来发送数据: 在这个函数中可以看到主要是读取状态区分读写 和 地址位宽
static int cdns_i2c_process_msg(struct cdns_i2c *id, struct i2c_msg *msg,
struct i2c_adapter *adap)
{
unsigned long time_left;
u32 reg;
id->p_msg = msg; //保存了要发送的消息到struct cdns_i2c 结构体
id->err_status = 0;
reinit_completion(&id->xfer_done); //把id->xfer_done设置为0
/* Check for the TEN Bit mode on each msg */
reg = cdns_i2c_readreg(CDNS_I2C_CR_OFFSET); //读取控制寄存器 0x00
if (msg->flags & I2C_M_TEN) { //10位地址
if (reg & CDNS_I2C_CR_NEA)
cdns_i2c_writereg(reg & ~CDNS_I2C_CR_NEA,
CDNS_I2C_CR_OFFSET);
} else { //7位地址
if (!(reg & CDNS_I2C_CR_NEA))
cdns_i2c_writereg(reg | CDNS_I2C_CR_NEA,
CDNS_I2C_CR_OFFSET);
}
/* Check for zero length - Slave monitor mode */
if (msg->len == 0)
cdns_i2c_slvmon(id);
/* Check for the R/W flag on each msg */
else if (msg->flags & I2C_M_RD) //主机读取从设备的话 I2C_M_RD= 1;
cdns_i2c_mrecv(id); //接收数据
else
cdns_i2c_msend(id); //发送数据
/* Wait for the signal of completion */
time_left = wait_for_completion_timeout(&id->xfer_done, adap->timeout); //等待id->xfer_done 置位
if (time_left == 0) {
i2c_recover_bus(adap);
cdns_i2c_master_reset(adap);
dev_err(id->adap.dev.parent, "timeout waiting on completion\n");
return -ETIMEDOUT;
}
cdns_i2c_writereg(CDNS_I2C_IXR_ALL_INTR_MASK, CDNS_I2C_IDR_OFFSET);
/* If it is bus arbitration error, try again */
if (id->err_status & CDNS_I2C_IXR_ARB_LOST)
return -EAGAIN;
return 0;
}
cdns_i2c_mrecv: 一路下来终于到了接收数据的地方 (做了一下删减)
使能中断cdns_i2c_writereg(CDNS_I2C_ENABLED_INTR_MASK, CDNS_I2C_IER_OFFSET);
static void cdns_i2c_mrecv(struct cdns_i2c *id)
{
unsigned int ctrl_reg;
unsigned int isr_status;
id->p_recv_buf = id->p_msg->buf; //保存
id->recv_count = id->p_msg->len;
/* Put the controller in master receive mode and clear the FIFO */
ctrl_reg = cdns_i2c_readreg(CDNS_I2C_CR_OFFSET);
ctrl_reg |= CDNS_I2C_CR_RW | CDNS_I2C_CR_CLR_FIFO;
if (id->p_msg->flags & I2C_M_RECV_LEN)
id->recv_count = I2C_SMBUS_BLOCK_MAX + 1;
id->curr_recv_count = id->recv_count;
检查iic中接收数据的长度比fifo的深度大,需要多次传输;
if (id->recv_count > CDNS_I2C_FIFO_DEPTH) ctrl_reg |= CDNS_I2C_CR_HOLD;
cdns_i2c_writereg(ctrl_reg, CDNS_I2C_CR_OFFSET);
//清除中断状态寄存器中的中断: 0x10
isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
if (id->recv_count > CDNS_I2C_TRANSFER_SIZE) { //传输大小判断 0x14寄存器
cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE, CDNS_I2C_XFER_SIZE_OFFSET);
id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE;
} else { cdns_i2c_writereg(id->recv_count, CDNS_I2C_XFER_SIZE_OFFSET); }
//中断使能 bit[ 0 1 2 5 6 7 9]置位 0x24中断使能寄存器
cdns_i2c_writereg(CDNS_I2C_ENABLED_INTR_MASK, CDNS_I2C_IER_OFFSET);
//从设备地址 写入到 0x08 IIC地址寄存器
cdns_i2c_writereg(id->p_msg->addr & CDNS_I2C_ADDR_MASK, CDNS_I2C_ADDR_OFFSET);
/* Clear the bus hold flag if bytes to receive is less than FIFO size */
if (!id->bus_hold_flag && ((id->p_msg->flags & I2C_M_RECV_LEN) != I2C_M_RECV_LEN) &&
(id->recv_count <= CDNS_I2C_FIFO_DEPTH))
cdns_i2c_clear_bus_hold(id);
}
看完这个函数 发现cdns_i2c_mrecv中没有对数据寄存器(0x0C)操作怎么读取数据?? cdns_i2c_process_msg里它使能了中断又等待id->xfer_done置位;搜索下就知道是在中断中传输的数据;中断的处理函数在probe中已经注册好了,还没有分析;
分析中断处理函数:中断函数最后对id->xfer_done做了置位; cdns_i2c_process_msg中等待判断这个标识置位
cdns_i2c_isr中断处理函数:
static irqreturn_t cdns_i2c_isr(int irq, void *ptr){
cdns_i2c_master_isr(ptr);
return IRQ_HANDLED;
}
static irqreturn_t cdns_i2c_master_isr(void *ptr) //最终对i2c控制寄存器操作的函数
{
unsigned int isr_status, avail_bytes, updatetx;
unsigned int bytes_to_send;
bool hold_quirk;
struct cdns_i2c *id = ptr;
/* Signal completion only after everything is updated */
int done_flag = 0;
irqreturn_t status = IRQ_NONE;
/* 读取中断状态,清空中断状态 */
isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
/* 检测中断状态 nack表示从机响应,或者主机正在仲裁,直接返回*/
if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_ARB_LOST)) {
done_flag = 1; status = IRQ_HANDLED;
}
updatetx = 0;
if (id->recv_count > id->curr_recv_count)
updatetx = 1;
hold_quirk = (id->quirks & CDNS_I2C_BROKEN_HOLD_BIT) && updatetx;
/* When receiving, handle data interrupt and completion interrupt */
if (id->p_recv_buf &&
((isr_status & CDNS_I2C_IXR_COMP) ||
(isr_status & CDNS_I2C_IXR_DATA))) {
/* 读取数据 先读取状态寄存器 为读取数据有效 fifo深度*/
while(cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) & CDNS_I2C_SR_RXDV) {
if((id->recv_count<CDNS_I2C_FIFO_DEPTH)&&!id->bus_hold_flag)
cdns_i2c_clear_bus_hold(id);
/* 读取数据寄存器填充到 p_recv_buf中 */
*(id->p_recv_buf)++ = cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
id->recv_count--;
id->curr_recv_count--;
if (cdns_is_holdquirk(id, hold_quirk)) break;
}
/*大型数据传输*/
if (cdns_is_holdquirk(id, hold_quirk)) {
/* wait while fifo is full */
while (cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET) !=
(id->curr_recv_count - CDNS_I2C_FIFO_DEPTH));
if(((int)(id->recv_count)-CDNS_I2C_FIFO_DEPTH) >
CDNS_I2C_TRANSFER_SIZE){
cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE,
CDNS_I2C_XFER_SIZE_OFFSET);
id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE +
CDNS_I2C_FIFO_DEPTH;
} else {
cdns_i2c_writereg(id->recv_count -
CDNS_I2C_FIFO_DEPTH,
CDNS_I2C_XFER_SIZE_OFFSET);
id->curr_recv_count = id->recv_count;
}
} else if (id->recv_count && !hold_quirk &&
!id->curr_recv_count) {
/* Set the slave address in address register*/
cdns_i2c_writereg(id->p_msg->addr & CDNS_I2C_ADDR_MASK,
CDNS_I2C_ADDR_OFFSET);
if (id->recv_count > CDNS_I2C_TRANSFER_SIZE) {
cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE,
CDNS_I2C_XFER_SIZE_OFFSET);
id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE;
} else {
cdns_i2c_writereg(id->recv_count,
CDNS_I2C_XFER_SIZE_OFFSET);
id->curr_recv_count = id->recv_count;
}
}
/* Clear hold (if not repeated start) and signal completion */
if ((isr_status & CDNS_I2C_IXR_COMP) && !id->recv_count) {
if (!id->bus_hold_flag)
cdns_i2c_clear_bus_hold(id);
done_flag = 1;
}
status = IRQ_HANDLED;
}
/* When sending, handle transfer complete interrupt */
if ((isr_status & CDNS_I2C_IXR_COMP) && !id->p_recv_buf) {
if (id->send_count) {
avail_bytes = CDNS_I2C_FIFO_DEPTH -
cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET);
if (id->send_count > avail_bytes)
bytes_to_send = avail_bytes;
else
bytes_to_send = id->send_count;
while (bytes_to_send--) {
cdns_i2c_writereg(
(*(id->p_send_buf)++),
CDNS_I2C_DATA_OFFSET);
id->send_count--;
}
} else {
done_flag = 1;
}
if (!id->send_count && !id->bus_hold_flag)
cdns_i2c_clear_bus_hold(id);
status = IRQ_HANDLED;
}
/* Handling Slave monitor mode interrupt */
if (isr_status & CDNS_I2C_IXR_SLV_RDY) {
unsigned int ctrl_reg;
/* Read control register */
ctrl_reg = cdns_i2c_readreg(CDNS_I2C_CR_OFFSET);
/* Disable slave monitor mode */
ctrl_reg &= ~CDNS_I2C_CR_SLVMON;
cdns_i2c_writereg(ctrl_reg, CDNS_I2C_CR_OFFSET);
/* Clear interrupt flag for slvmon mode */
cdns_i2c_writereg(CDNS_I2C_IXR_SLV_RDY, CDNS_I2C_IDR_OFFSET);
done_flag = 1;
status = IRQ_HANDLED;
}
/* 保存错误标识,在cdns_i2c_process_msg中判断处理 */
id->err_status = isr_status & CDNS_I2C_IXR_ERR_INTR_MASK;
if (id->err_status)
status = IRQ_HANDLED;
/*对数据处理完成 cdns_i2c_process_msg中等待判断这个标识置位 */
if (done_flag)
complete(&id->xfer_done);
return status;
}
流程理一下;
cdns_i2c_probe 通过设备树匹配到probe
获取设备树资源(终端号,i2c基地址 时钟等等);
初始化adapter 结构
初始化alogrithm id->adap.algo = &cdns_i2c_algo;
注册中断
注册adapter
设备驱动呢怎么使用adapter 和 alogrithm?
简单看个例子:
static const struct i2c_device_id tmp401_id[] = {
{ "tmp401", tmp401 },
{ "tmp411", tmp411 },
};
static struct i2c_driver tmp401_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "tmp401",
.of_match_table = of_match_ptr(tmp401_of_match),
},
.probe = tmp401_probe,
.id_table = tmp401_id,
.detect = tmp401_detect,
.address_list = normal_i2c,
};
匹配后执行: static int tmp401_probe(struct i2c_client *client, const struct i2c_device_id *id)
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; // 这个不就是我们控制器中初始化的过的嘛用它来产生中断收发数据
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
看下probe函数: 做了删减
static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0);
static const struct attribute_group tmp401_group = {
.attrs = tmp401_attributes,
};
static struct attribute *tmp401_attributes[] = {
&sensor_dev_attr_temp1_input.dev_attr.attr,
NULL
}
static int tmp401_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
static const char * const names[] = {
"TMP401", "TMP411", "TMP431", "TMP432", "TMP435", "TMP461"
};
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct tmp401_data *data;
int groups = 0, status;
data = devm_kzalloc(dev, sizeof(struct tmp401_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->update_lock);
data->kind = id->driver_data;
/* Initialize the TMP401 chip */
status = tmp401_init_client(data, client);
if (status < 0)
return status;
/* Register sysfs hooks */
data->groups[groups++] = &tmp401_group;
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
data, data->groups);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
dev_info(dev, "Detected TI %s chip\n", names[data->kind]);
return 0;
}
从设备读取温度接口:
static ssize_t show_temp_crit_hyst(struct device *dev,
struct device_attribute *devattr, char *buf)
{
int temp, index = to_sensor_dev_attr(devattr)->index;
struct tmp401_data *data = tmp401_update_device(dev);
if (IS_ERR(data))
return PTR_ERR(data);
mutex_lock(&data->update_lock);
temp = tmp401_register_to_temp(data->temp[3][index], data->config);
temp -= data->temp_crit_hyst * 1000;
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", temp);
}
static struct tmp401_data *tmp401_update_device(struct device *dev)
{
struct tmp401_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
struct tmp401_data *ret = data;
int i, val;
unsigned long next_update;
mutex_lock(&data->update_lock);
next_update = data->last_updated +
msecs_to_jiffies(data->update_interval);
if (time_after(jiffies, next_update) || !data->valid) {
if (data->kind != tmp432) {
/*
* The driver uses the TMP432 status format internally.
* Convert status to TMP432 format for other chips.
*/
val = i2c_smbus_read_byte_data(client, TMP401_STATUS);
if (val < 0) {
ret = ERR_PTR(val);
goto abort;
}
data->status[0] =
(val & TMP401_STATUS_REMOTE_OPEN) >> 1;
data->status[1] =
((val & TMP401_STATUS_REMOTE_LOW) >> 2) |
((val & TMP401_STATUS_LOCAL_LOW) >> 5);
data->status[2] =
((val & TMP401_STATUS_REMOTE_HIGH) >> 3) |
((val & TMP401_STATUS_LOCAL_HIGH) >> 6);
data->status[3] = val & (TMP401_STATUS_LOCAL_CRIT
| TMP401_STATUS_REMOTE_CRIT);
} else {
for (i = 0; i < ARRAY_SIZE(data->status); i++) {
val = i2c_smbus_read_byte_data(client,
TMP432_STATUS_REG[i]);
if (val < 0) {
ret = ERR_PTR(val);
goto abort;
}
data->status[i] = val;
}
}
val = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ);
if (val < 0) {
ret = ERR_PTR(val);
goto abort;
}
data->config = val;
val = tmp401_update_device_reg16(client, data);
if (val < 0) {
ret = ERR_PTR(val);
goto abort;
}
val = i2c_smbus_read_byte_data(client, TMP401_TEMP_CRIT_HYST);
if (val < 0) {
ret = ERR_PTR(val);
goto abort;
}
data->temp_crit_hyst = val;
data->last_updated = jiffies;
data->valid = 1;
}
abort:
mutex_unlock(&data->update_lock);
return ret;
}