diff options
Diffstat (limited to 'drivers/i3c/device.c')
| -rw-r--r-- | drivers/i3c/device.c | 262 | 
1 files changed, 262 insertions, 0 deletions
| diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c new file mode 100644 index 00000000000..32c57bccc42 --- /dev/null +++ b/drivers/i3c/device.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#include <linux/bug.h> +#include <linux/completion.h> +#include <dm/device.h> + +#include "internals.h" + +/* i3c */ + +#define I3C_MATCH_DCR			0x1 +#define I3C_MATCH_MANUF			0x2 +#define I3C_MATCH_PART			0x4 +#define I3C_MATCH_EXTRA_INFO		0x8 + +struct i3c_device_id { +	__u8 match_flags; +	__u8 dcr; +	__u16 manuf_id; +	__u16 part_id; +	__u16 extra_info; + +	const void *data; +}; + +/** + * i3c_device_do_priv_xfers() - do I3C SDR private transfers directed to a + *				specific device + * + * @dev: device with which the transfers should be done + * @xfers: array of transfers + * @nxfers: number of transfers + * + * Initiate one or several private SDR transfers with @dev. + * + * This function can sleep and thus cannot be called in atomic context. + * + * Return: 0 in case of success, a negative error core otherwise. + */ +int i3c_device_do_priv_xfers(struct i3c_device *dev, +			     struct i3c_priv_xfer *xfers, +			     int nxfers) +{ +	int ret, i; + +	if (nxfers < 1) +		return 0; + +	for (i = 0; i < nxfers; i++) { +		if (!xfers[i].len || !xfers[i].data.in) +			return -EINVAL; +	} + +	i3c_bus_normaluse_lock(dev->bus); +	ret = i3c_dev_do_priv_xfers_locked(dev->desc, xfers, nxfers); +	i3c_bus_normaluse_unlock(dev->bus); + +	return ret; +} +EXPORT_SYMBOL_GPL(i3c_device_do_priv_xfers); + +/** + * i3c_device_get_info() - get I3C device information + * + * @dev: device we want information on + * @info: the information object to fill in + * + * Retrieve I3C dev info. + */ +void i3c_device_get_info(struct i3c_device *dev, +			 struct i3c_device_info *info) +{ +	if (!info) +		return; + +	i3c_bus_normaluse_lock(dev->bus); +	if (dev->desc) +		*info = dev->desc->info; +	i3c_bus_normaluse_unlock(dev->bus); +} +EXPORT_SYMBOL_GPL(i3c_device_get_info); + +/** + * i3c_device_disable_ibi() - Disable IBIs coming from a specific device + * @dev: device on which IBIs should be disabled + * + * This function disable IBIs coming from a specific device and wait for + * all pending IBIs to be processed. + * + * Return: 0 in case of success, a negative error core otherwise. + */ +int i3c_device_disable_ibi(struct i3c_device *dev) +{ +	int ret = -ENOENT; + +	i3c_bus_normaluse_lock(dev->bus); +	if (dev->desc) { +		mutex_lock(&dev->desc->ibi_lock); +		ret = i3c_dev_disable_ibi_locked(dev->desc); +		mutex_unlock(&dev->desc->ibi_lock); +	} +	i3c_bus_normaluse_unlock(dev->bus); + +	return ret; +} +EXPORT_SYMBOL_GPL(i3c_device_disable_ibi); + +/** + * i3c_device_enable_ibi() - Enable IBIs coming from a specific device + * @dev: device on which IBIs should be enabled + * + * This function enable IBIs coming from a specific device and wait for + * all pending IBIs to be processed. This should be called on a device + * where i3c_device_request_ibi() has succeeded. + * + * Note that IBIs from this device might be received before this function + * returns to its caller. + * + * Return: 0 in case of success, a negative error core otherwise. + */ +int i3c_device_enable_ibi(struct i3c_device *dev) +{ +	int ret = -ENOENT; + +	i3c_bus_normaluse_lock(dev->bus); +	if (dev->desc) { +		mutex_lock(&dev->desc->ibi_lock); +		ret = i3c_dev_enable_ibi_locked(dev->desc); +		mutex_unlock(&dev->desc->ibi_lock); +	} +	i3c_bus_normaluse_unlock(dev->bus); + +	return ret; +} +EXPORT_SYMBOL_GPL(i3c_device_enable_ibi); + +/** + * i3c_device_request_ibi() - Request an IBI + * @dev: device for which we should enable IBIs + * @req: setup requested for this IBI + * + * This function is responsible for pre-allocating all resources needed to + * process IBIs coming from @dev. When this function returns, the IBI is not + * enabled until i3c_device_enable_ibi() is called. + * + * Return: 0 in case of success, a negative error core otherwise. + */ +int i3c_device_request_ibi(struct i3c_device *dev, +			   const struct i3c_ibi_setup *req) +{ +	int ret = -ENOENT; + +	if (!req->handler || !req->num_slots) +		return -EINVAL; + +	i3c_bus_normaluse_lock(dev->bus); +	if (dev->desc) { +		mutex_lock(&dev->desc->ibi_lock); +		ret = i3c_dev_request_ibi_locked(dev->desc, req); +		mutex_unlock(&dev->desc->ibi_lock); +	} +	i3c_bus_normaluse_unlock(dev->bus); + +	return ret; +} +EXPORT_SYMBOL_GPL(i3c_device_request_ibi); + +/** + * i3c_device_free_ibi() - Free all resources needed for IBI handling + * @dev: device on which you want to release IBI resources + * + * This function is responsible for de-allocating resources previously + * allocated by i3c_device_request_ibi(). It should be called after disabling + * IBIs with i3c_device_disable_ibi(). + */ +void i3c_device_free_ibi(struct i3c_device *dev) +{ +	i3c_bus_normaluse_lock(dev->bus); +	if (dev->desc) { +		mutex_lock(&dev->desc->ibi_lock); +		i3c_dev_free_ibi_locked(dev->desc); +		mutex_unlock(&dev->desc->ibi_lock); +	} +	i3c_bus_normaluse_unlock(dev->bus); +} +EXPORT_SYMBOL_GPL(i3c_device_free_ibi); + +/** + * i3cdev_to_dev() - Returns the device embedded in @i3cdev + * @i3cdev: I3C device + * + * Return: a pointer to a device object. + */ +struct udevice *i3cdev_to_dev(struct i3c_device *i3cdev) +{ +	return &i3cdev->dev; +} +EXPORT_SYMBOL_GPL(i3cdev_to_dev); + +/** + * dev_to_i3cdev() - Returns the I3C device containing @dev + * @dev: device object + * + * Return: a pointer to an I3C device object. + */ +struct i3c_device *dev_to_i3cdev(struct udevice *dev) +{ +	return container_of(dev, struct i3c_device, dev); +} +EXPORT_SYMBOL_GPL(dev_to_i3cdev); + +/** + * i3c_device_match_id() - Returns the i3c_device_id entry matching @i3cdev + * @i3cdev: I3C device + * @id_table: I3C device match table + * + * Return: a pointer to an i3c_device_id object or NULL if there's no match. + */ +const struct i3c_device_id * +i3c_device_match_id(struct i3c_device *i3cdev, +		    const struct i3c_device_id *id_table) +{ +	struct i3c_device_info devinfo; +	const struct i3c_device_id *id; +	u16 manuf, part, ext_info; +	bool rndpid; + +	i3c_device_get_info(i3cdev, &devinfo); + +	manuf = I3C_PID_MANUF_ID(devinfo.pid); +	part = I3C_PID_PART_ID(devinfo.pid); +	ext_info = I3C_PID_EXTRA_INFO(devinfo.pid); +	rndpid = I3C_PID_RND_LOWER_32BITS(devinfo.pid); + +	for (id = id_table; id->match_flags != 0; id++) { +		if ((id->match_flags & I3C_MATCH_DCR) && +		    id->dcr != devinfo.dcr) +			continue; + +		if ((id->match_flags & I3C_MATCH_MANUF) && +		    id->manuf_id != manuf) +			continue; + +		if ((id->match_flags & I3C_MATCH_PART) && +		    (rndpid || id->part_id != part)) +			continue; + +		if ((id->match_flags & I3C_MATCH_EXTRA_INFO) && +		    (rndpid || id->extra_info != ext_info)) +			continue; + +		return id; +	} + +	return NULL; +} +EXPORT_SYMBOL_GPL(i3c_device_match_id); | 
