diff options
Diffstat (limited to 'drivers/i2c/i2c-core-smbus.c')
| -rw-r--r-- | drivers/i2c/i2c-core-smbus.c | 154 |
1 files changed, 88 insertions, 66 deletions
diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c index 9cd66cabb84f..71eb1ef56f0c 100644 --- a/drivers/i2c/i2c-core-smbus.c +++ b/drivers/i2c/i2c-core-smbus.c @@ -1,24 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Linux I2C core SMBus and SMBus emulation code * * This file contains the SMBus functions which are always included in the I2C * core because they can be emulated via I2C. SMBus specific extensions - * (e.g. smbalert) are handled in a seperate i2c-smbus module. + * (e.g. smbalert) are handled in a separate i2c-smbus module. * * All SMBus-related things are written by Frodo Looijaard <frodol@dds.nl> * SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and * Jean Delvare <jdelvare@suse.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. */ #include <linux/device.h> #include <linux/err.h> #include <linux/i2c.h> #include <linux/i2c-smbus.h> +#include <linux/property.h> #include <linux/slab.h> +#include <linux/string_choices.h> + +#include "i2c-core.h" #define CREATE_TRACE_POINTS #include <trace/events/smbus.h> @@ -39,8 +39,15 @@ static u8 crc8(u16 data) return (u8)(data >> 8); } -/* Incremental CRC8 over count bytes in the array pointed to by p */ -static u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count) +/** + * i2c_smbus_pec - Incremental CRC8 over the given input data array + * @crc: previous return crc8 value + * @p: pointer to data buffer. + * @count: number of bytes in data buffer. + * + * Incremental CRC8 over count bytes in the array pointed to by p + */ +u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count) { int i; @@ -48,6 +55,7 @@ static u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count) crc = crc8((crc ^ p[i]) << 8); return crc; } +EXPORT_SYMBOL(i2c_smbus_pec); /* Assume a 7-bit address, which is reasonable for SMBus */ static u8 i2c_smbus_msg_pec(u8 pec, struct i2c_msg *msg) @@ -115,7 +123,7 @@ EXPORT_SYMBOL(i2c_smbus_read_byte); s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value) { return i2c_smbus_xfer(client->adapter, client->addr, client->flags, - I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL); + I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL); } EXPORT_SYMBOL(i2c_smbus_write_byte); @@ -325,8 +333,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, */ unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; - int num = read_write == I2C_SMBUS_READ ? 2 : 1; - int i; + int nmsgs = read_write == I2C_SMBUS_READ ? 2 : 1; u8 partial_pec = 0; int status; struct i2c_msg msg[2] = { @@ -342,6 +349,8 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, .buf = msgbuf1, }, }; + bool wants_pec = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK + && size != I2C_SMBUS_I2C_BLOCK_DATA); msgbuf0[0] = command; switch (size) { @@ -350,13 +359,13 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, /* Special case: The read/write field is used as data */ msg[0].flags = flags | (read_write == I2C_SMBUS_READ ? I2C_M_RD : 0); - num = 1; + nmsgs = 1; break; case I2C_SMBUS_BYTE: if (read_write == I2C_SMBUS_READ) { /* Special case: only a read! */ msg[0].flags = I2C_M_RD | flags; - num = 1; + nmsgs = 1; } break; case I2C_SMBUS_BYTE_DATA: @@ -377,7 +386,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, } break; case I2C_SMBUS_PROC_CALL: - num = 2; /* Special case */ + nmsgs = 2; /* Special case */ read_write = I2C_SMBUS_READ; msg[0].len = 3; msg[1].len = 2; @@ -400,12 +409,11 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, } i2c_smbus_try_get_dmabuf(&msg[0], command); - for (i = 1; i < msg[0].len; i++) - msg[0].buf[i] = data->block[i - 1]; + memcpy(msg[0].buf + 1, data->block, msg[0].len - 1); } break; case I2C_SMBUS_BLOCK_PROC_CALL: - num = 2; /* Another special case */ + nmsgs = 2; /* Another special case */ read_write = I2C_SMBUS_READ; if (data->block[0] > I2C_SMBUS_BLOCK_MAX) { dev_err(&adapter->dev, @@ -416,8 +424,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, msg[0].len = data->block[0] + 2; i2c_smbus_try_get_dmabuf(&msg[0], command); - for (i = 1; i < msg[0].len; i++) - msg[0].buf[i] = data->block[i - 1]; + memcpy(msg[0].buf + 1, data->block, msg[0].len - 1); msg[1].flags |= I2C_M_RECV_LEN; msg[1].len = 1; /* block length will be added by @@ -427,7 +434,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, case I2C_SMBUS_I2C_BLOCK_DATA: if (data->block[0] > I2C_SMBUS_BLOCK_MAX) { dev_err(&adapter->dev, "Invalid block %s size %d\n", - read_write == I2C_SMBUS_READ ? "read" : "write", + str_read_write(read_write == I2C_SMBUS_READ), data->block[0]); return -EINVAL; } @@ -439,8 +446,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, msg[0].len = data->block[0] + 1; i2c_smbus_try_get_dmabuf(&msg[0], command); - for (i = 1; i <= data->block[0]; i++) - msg[0].buf[i] = data->block[i]; + memcpy(msg[0].buf + 1, data->block + 1, data->block[0]); } break; default: @@ -448,33 +454,31 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, return -EOPNOTSUPP; } - i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK - && size != I2C_SMBUS_I2C_BLOCK_DATA); - if (i) { + if (wants_pec) { /* Compute PEC if first message is a write */ if (!(msg[0].flags & I2C_M_RD)) { - if (num == 1) /* Write only */ + if (nmsgs == 1) /* Write only */ i2c_smbus_add_pec(&msg[0]); else /* Write followed by read */ partial_pec = i2c_smbus_msg_pec(0, &msg[0]); } /* Ask for PEC if last message is a read */ - if (msg[num-1].flags & I2C_M_RD) - msg[num-1].len++; + if (msg[nmsgs - 1].flags & I2C_M_RD) + msg[nmsgs - 1].len++; } - status = __i2c_transfer(adapter, msg, num); + status = __i2c_transfer(adapter, msg, nmsgs); if (status < 0) goto cleanup; - if (status != num) { + if (status != nmsgs) { status = -EIO; goto cleanup; } status = 0; /* Check PEC if last message is a read */ - if (i && (msg[num-1].flags & I2C_M_RD)) { - status = i2c_smbus_check_pec(partial_pec, &msg[num-1]); + if (wants_pec && (msg[nmsgs - 1].flags & I2C_M_RD)) { + status = i2c_smbus_check_pec(partial_pec, &msg[nmsgs - 1]); if (status < 0) goto cleanup; } @@ -492,13 +496,18 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, data->word = msgbuf1[0] | (msgbuf1[1] << 8); break; case I2C_SMBUS_I2C_BLOCK_DATA: - for (i = 0; i < data->block[0]; i++) - data->block[i + 1] = msg[1].buf[i]; + memcpy(data->block + 1, msg[1].buf, data->block[0]); break; case I2C_SMBUS_BLOCK_DATA: case I2C_SMBUS_BLOCK_PROC_CALL: - for (i = 0; i < msg[1].buf[0] + 1; i++) - data->block[i] = msg[1].buf[i]; + if (msg[1].buf[0] > I2C_SMBUS_BLOCK_MAX) { + dev_err(&adapter->dev, + "Invalid block size returned: %d\n", + msg[1].buf[0]); + status = -EPROTO; + goto cleanup; + } + memcpy(data->block, msg[1].buf, msg[1].buf[0] + 1); break; } @@ -530,7 +539,10 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, { s32 res; - i2c_lock_bus(adapter, I2C_LOCK_SEGMENT); + res = __i2c_lock_bus_helper(adapter); + if (res) + return res; + res = __i2c_smbus_xfer(adapter, addr, flags, read_write, command, protocol, data); i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT); @@ -543,10 +555,17 @@ s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, char read_write, u8 command, int protocol, union i2c_smbus_data *data) { + int (*xfer_func)(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data); unsigned long orig_jiffies; int try; s32 res; + res = __i2c_check_suspended(adapter); + if (res) + return res; + /* If enabled, the following two tracepoints are conditional on * read_write and protocol. */ @@ -557,13 +576,20 @@ s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB; - if (adapter->algo->smbus_xfer) { + xfer_func = adapter->algo->smbus_xfer; + if (i2c_in_atomic_xfer_mode()) { + if (adapter->algo->smbus_xfer_atomic) + xfer_func = adapter->algo->smbus_xfer_atomic; + else if (adapter->algo->master_xfer_atomic) + xfer_func = NULL; /* fallback to I2C emulation */ + } + + if (xfer_func) { /* Retry automatically on arbitration loss */ orig_jiffies = jiffies; for (res = 0, try = 0; try <= adapter->retries; try++) { - res = adapter->algo->smbus_xfer(adapter, addr, flags, - read_write, command, - protocol, data); + res = xfer_func(adapter, addr, flags, read_write, + command, protocol, data); if (res != -EAGAIN) break; if (time_after(jiffies, @@ -585,7 +611,7 @@ s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, trace: /* If enabled, the reply tracepoint is conditional on read_write. */ trace_smbus_reply(adapter, addr, flags, read_write, - command, protocol, data); + command, protocol, data, res); trace_smbus_result(adapter, addr, flags, read_write, command, protocol, res); @@ -651,7 +677,7 @@ s32 i2c_smbus_read_i2c_block_data_or_emulated(const struct i2c_client *client, EXPORT_SYMBOL(i2c_smbus_read_i2c_block_data_or_emulated); /** - * i2c_setup_smbus_alert - Setup SMBus alert support + * i2c_new_smbus_alert_device - get ara client for SMBus alert support * @adapter: the target adapter * @setup: setup data for the SMBus alert handler * Context: can sleep @@ -661,45 +687,41 @@ EXPORT_SYMBOL(i2c_smbus_read_i2c_block_data_or_emulated); * Handling can be done either through our IRQ handler, or by the * adapter (from its handler, periodic polling, or whatever). * - * NOTE that if we manage the IRQ, we *MUST* know if it's level or - * edge triggered in order to hand it to the workqueue correctly. - * If triggering the alert seems to wedge the system, you probably - * should have said it's level triggered. - * * This returns the ara client, which should be saved for later use with - * i2c_handle_smbus_alert() and ultimately i2c_unregister_device(); or NULL - * to indicate an error. + * i2c_handle_smbus_alert() and ultimately i2c_unregister_device(); or an + * ERRPTR to indicate an error. */ -struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter, - struct i2c_smbus_alert_setup *setup) +struct i2c_client *i2c_new_smbus_alert_device(struct i2c_adapter *adapter, + struct i2c_smbus_alert_setup *setup) { struct i2c_board_info ara_board_info = { I2C_BOARD_INFO("smbus_alert", 0x0c), .platform_data = setup, }; - return i2c_new_device(adapter, &ara_board_info); + return i2c_new_client_device(adapter, &ara_board_info); } -EXPORT_SYMBOL_GPL(i2c_setup_smbus_alert); +EXPORT_SYMBOL_GPL(i2c_new_smbus_alert_device); -#if IS_ENABLED(CONFIG_I2C_SMBUS) && IS_ENABLED(CONFIG_OF) -int of_i2c_setup_smbus_alert(struct i2c_adapter *adapter) +#if IS_ENABLED(CONFIG_I2C_SMBUS) +int i2c_setup_smbus_alert(struct i2c_adapter *adapter) { - struct i2c_client *client; + struct device *parent = adapter->dev.parent; int irq; - irq = of_property_match_string(adapter->dev.of_node, "interrupt-names", - "smbus_alert"); - if (irq == -EINVAL || irq == -ENODATA) + /* Adapter instantiated without parent, skip the SMBus alert setup */ + if (!parent) return 0; - else if (irq < 0) + + /* Report serious errors */ + irq = device_property_match_string(parent, "interrupt-names", "smbus_alert"); + if (irq < 0 && irq != -EINVAL && irq != -ENODATA) return irq; - client = i2c_setup_smbus_alert(adapter, NULL); - if (!client) - return -ENODEV; + /* Skip setup when no irq was found */ + if (irq < 0 && !device_property_present(parent, "smbalert-gpios")) + return 0; - return 0; + return PTR_ERR_OR_ZERO(i2c_new_smbus_alert_device(adapter, NULL)); } -EXPORT_SYMBOL_GPL(of_i2c_setup_smbus_alert); #endif |
