Commit 2a71593d authored by Alain Volmat's avatar Alain Volmat Committed by Wolfram Sang
Browse files

i2c: smbus: add core function handling SMBus host-notify



SMBus Host-Notify protocol, from the adapter point of view
consist of receiving a message from a client, including the
client address and some other data.

It can be simply handled by creating a new slave device
and registering a callback performing the parsing of the
message received from the client.

This commit introduces two new core functions
  * i2c_new_slave_host_notify_device
  * i2c_free_slave_host_notify_device
that take care of registration of the new slave device and
callback and will call i2c_handle_smbus_host_notify once a
Host-Notify event is received.

Signed-off-by: default avatarAlain Volmat <alain.volmat@st.com>
Reviewed-by: default avatarPierre-Yves MORDRET <pierre-yves.mordret@st.com>
Signed-off-by: default avatarWolfram Sang <wsa@kernel.org>
parent e6277308
Loading
Loading
Loading
Loading
+107 −0
Original line number Diff line number Diff line
@@ -197,6 +197,113 @@ EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert);

module_i2c_driver(smbalert_driver);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
#define SMBUS_HOST_NOTIFY_LEN	3
struct i2c_slave_host_notify_status {
	u8 index;
	u8 addr;
};

static int i2c_slave_host_notify_cb(struct i2c_client *client,
				    enum i2c_slave_event event, u8 *val)
{
	struct i2c_slave_host_notify_status *status = client->dev.platform_data;

	switch (event) {
	case I2C_SLAVE_WRITE_RECEIVED:
		/* We only retrieve the first byte received (addr)
		 * since there is currently no support to retrieve the data
		 * parameter from the client.
		 */
		if (status->index == 0)
			status->addr = *val;
		if (status->index < U8_MAX)
			status->index++;
		break;
	case I2C_SLAVE_STOP:
		if (status->index == SMBUS_HOST_NOTIFY_LEN)
			i2c_handle_smbus_host_notify(client->adapter,
						     status->addr);
		fallthrough;
	case I2C_SLAVE_WRITE_REQUESTED:
		status->index = 0;
		break;
	case I2C_SLAVE_READ_REQUESTED:
	case I2C_SLAVE_READ_PROCESSED:
		*val = 0xff;
		break;
	}

	return 0;
}

/**
 * i2c_new_slave_host_notify_device - get a client for SMBus host-notify support
 * @adapter: the target adapter
 * Context: can sleep
 *
 * Setup handling of the SMBus host-notify protocol on a given I2C bus segment.
 *
 * Handling is done by creating a device and its callback and handling data
 * received via the SMBus host-notify address (0x8)
 *
 * This returns the client, which should be ultimately freed using
 * i2c_free_slave_host_notify_device(); or an ERRPTR to indicate an error.
 */
struct i2c_client *i2c_new_slave_host_notify_device(struct i2c_adapter *adapter)
{
	struct i2c_board_info host_notify_board_info = {
		I2C_BOARD_INFO("smbus_host_notify", 0x08),
		.flags  = I2C_CLIENT_SLAVE,
	};
	struct i2c_slave_host_notify_status *status;
	struct i2c_client *client;
	int ret;

	status = kzalloc(sizeof(struct i2c_slave_host_notify_status),
			 GFP_KERNEL);
	if (!status)
		return ERR_PTR(-ENOMEM);

	host_notify_board_info.platform_data = status;

	client = i2c_new_client_device(adapter, &host_notify_board_info);
	if (IS_ERR(client)) {
		kfree(status);
		return client;
	}

	ret = i2c_slave_register(client, i2c_slave_host_notify_cb);
	if (ret) {
		i2c_unregister_device(client);
		kfree(status);
		return ERR_PTR(ret);
	}

	return client;
}
EXPORT_SYMBOL_GPL(i2c_new_slave_host_notify_device);

/**
 * i2c_free_slave_host_notify_device - free the client for SMBus host-notify
 * support
 * @client: the client to free
 * Context: can sleep
 *
 * Free the i2c_client allocated via i2c_new_slave_host_notify_device
 */
void i2c_free_slave_host_notify_device(struct i2c_client *client)
{
	if (IS_ERR_OR_NULL(client))
		return;

	i2c_slave_unregister(client);
	kfree(client->dev.platform_data);
	i2c_unregister_device(client);
}
EXPORT_SYMBOL_GPL(i2c_free_slave_host_notify_device);
#endif

/*
 * SPD is not part of SMBus but we include it here for convenience as the
 * target systems are the same.
+12 −0
Original line number Diff line number Diff line
@@ -38,6 +38,18 @@ static inline int of_i2c_setup_smbus_alert(struct i2c_adapter *adap)
	return 0;
}
#endif
#if IS_ENABLED(CONFIG_I2C_SMBUS) && IS_ENABLED(CONFIG_I2C_SLAVE)
struct i2c_client *i2c_new_slave_host_notify_device(struct i2c_adapter *adapter);
void i2c_free_slave_host_notify_device(struct i2c_client *client);
#else
static inline struct i2c_client *i2c_new_slave_host_notify_device(struct i2c_adapter *adapter)
{
	return ERR_PTR(-ENOSYS);
}
static inline void i2c_free_slave_host_notify_device(struct i2c_client *client)
{
}
#endif

#if IS_ENABLED(CONFIG_I2C_SMBUS) && IS_ENABLED(CONFIG_DMI)
void i2c_register_spd(struct i2c_adapter *adap);