diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-12-25 12:26:34 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-12-25 12:26:34 -0800 |
commit | b3cc2bfe7244e848f5e8caa77bbdc72c04abd17c (patch) | |
tree | 19cce6c02dcdb290ef238fb3e17698cc915d4533 /drivers | |
parent | 4971f090aa7f6ce5daa094ce4334f6618f93a7eb (diff) | |
parent | 25ac3da61ba144f8dbfe377eeec6b1da7ad0854a (diff) | |
download | linux-b3cc2bfe7244e848f5e8caa77bbdc72c04abd17c.tar.gz linux-b3cc2bfe7244e848f5e8caa77bbdc72c04abd17c.tar.bz2 linux-b3cc2bfe7244e848f5e8caa77bbdc72c04abd17c.zip |
Merge tag 'i3c/for-4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull initial i3c support from Boris Brezillon:
"Add initial support for I3C along with two I3C master controller
drivers"
* tag 'i3c/for-4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux:
i3c: master: cdns: fix I2C transfers in Cadence I3C master driver
ic3: off by one in mode_show()
i3c: fix an error code in i3c_master_add_i3c_dev_locked()
i3c: master: dw: fix mask operation by using the correct operator
MAINTAINERS: Add myself as the dw-i3c-master module maintainer
dt-binding: i3c: Document Synopsys DesignWare I3C
i3c: master: Add driver for Synopsys DesignWare IP
i3c: master: Remove set but not used variable 'old_i3c_scl_lim'
dt-bindings: i3c: Document Cadence I3C master bindings
i3c: master: Add driver for Cadence IP
MAINTAINERS: Add myself as the I3C subsystem maintainer
dt-bindings: i3c: Document core bindings
i3c: Add sysfs ABI spec
docs: driver-api: Add I3C documentation
i3c: Add core I3C infrastructure
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/Kconfig | 2 | ||||
-rw-r--r-- | drivers/Makefile | 2 | ||||
-rw-r--r-- | drivers/i3c/Kconfig | 24 | ||||
-rw-r--r-- | drivers/i3c/Makefile | 4 | ||||
-rw-r--r-- | drivers/i3c/device.c | 233 | ||||
-rw-r--r-- | drivers/i3c/internals.h | 26 | ||||
-rw-r--r-- | drivers/i3c/master.c | 2659 | ||||
-rw-r--r-- | drivers/i3c/master/Kconfig | 22 | ||||
-rw-r--r-- | drivers/i3c/master/Makefile | 2 | ||||
-rw-r--r-- | drivers/i3c/master/dw-i3c-master.c | 1216 | ||||
-rw-r--r-- | drivers/i3c/master/i3c-master-cdns.c | 1666 |
11 files changed, 5855 insertions, 1 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index ab4d43923c4d..8395bc515996 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -57,6 +57,8 @@ source "drivers/char/Kconfig" source "drivers/i2c/Kconfig" +source "drivers/i3c/Kconfig" + source "drivers/spi/Kconfig" source "drivers/spmi/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 578f469f72fb..e1ce029d28fd 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -111,7 +111,7 @@ obj-$(CONFIG_SERIO) += input/serio/ obj-$(CONFIG_GAMEPORT) += input/gameport/ obj-$(CONFIG_INPUT) += input/ obj-$(CONFIG_RTC_LIB) += rtc/ -obj-y += i2c/ media/ +obj-y += i2c/ i3c/ media/ obj-$(CONFIG_PPS) += pps/ obj-y += ptp/ obj-$(CONFIG_W1) += w1/ diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig new file mode 100644 index 000000000000..30a441506f61 --- /dev/null +++ b/drivers/i3c/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +menuconfig I3C + tristate "I3C support" + select I2C + help + I3C is a serial protocol standardized by the MIPI alliance. + + It's supposed to be backward compatible with I2C while providing + support for high speed transfers and native interrupt support + without the need for extra pins. + + The I3C protocol also standardizes the slave device types and is + mainly designed to communicate with sensors. + + If you want I3C support, you should say Y here and also to the + specific driver for your bus adapter(s) below. + + This I3C support can also be built as a module. If so, the module + will be called i3c. + +if I3C +source "drivers/i3c/master/Kconfig" +endif # I3C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile new file mode 100644 index 000000000000..11982efbc6d9 --- /dev/null +++ b/drivers/i3c/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +i3c-y := device.o master.o +obj-$(CONFIG_I3C) += i3c.o +obj-$(CONFIG_I3C) += master/ diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c new file mode 100644 index 000000000000..69cc040c3a1c --- /dev/null +++ b/drivers/i3c/device.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#include <linux/atomic.h> +#include <linux/bug.h> +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include "internals.h" + +/** + * 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 device *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 device *dev) +{ + return container_of(dev, struct i3c_device, dev); +} +EXPORT_SYMBOL_GPL(dev_to_i3cdev); + +/** + * i3c_driver_register_with_owner() - register an I3C device driver + * + * @drv: driver to register + * @owner: module that owns this driver + * + * Register @drv to the core. + * + * Return: 0 in case of success, a negative error core otherwise. + */ +int i3c_driver_register_with_owner(struct i3c_driver *drv, struct module *owner) +{ + drv->driver.owner = owner; + drv->driver.bus = &i3c_bus_type; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(i3c_driver_register_with_owner); + +/** + * i3c_driver_unregister() - unregister an I3C device driver + * + * @drv: driver to unregister + * + * Unregister @drv. + */ +void i3c_driver_unregister(struct i3c_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(i3c_driver_unregister); diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h new file mode 100644 index 000000000000..86b7b44cfca2 --- /dev/null +++ b/drivers/i3c/internals.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#ifndef I3C_INTERNALS_H +#define I3C_INTERNALS_H + +#include <linux/i3c/master.h> + +extern struct bus_type i3c_bus_type; + +void i3c_bus_normaluse_lock(struct i3c_bus *bus); +void i3c_bus_normaluse_unlock(struct i3c_bus *bus); + +int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev, + struct i3c_priv_xfer *xfers, + int nxfers); +int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev); +int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev); +int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req); +void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev); +#endif /* I3C_INTERNAL_H */ diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c new file mode 100644 index 000000000000..c39f89d2deba --- /dev/null +++ b/drivers/i3c/master.c @@ -0,0 +1,2659 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + * + * Author: Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#include <linux/atomic.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> + +#include "internals.h" + +static DEFINE_IDR(i3c_bus_idr); +static DEFINE_MUTEX(i3c_core_lock); + +/** + * i3c_bus_maintenance_lock - Lock the bus for a maintenance operation + * @bus: I3C bus to take the lock on + * + * This function takes the bus lock so that no other operations can occur on + * the bus. This is needed for all kind of bus maintenance operation, like + * - enabling/disabling slave events + * - re-triggering DAA + * - changing the dynamic address of a device + * - relinquishing mastership + * - ... + * + * The reason for this kind of locking is that we don't want drivers and core + * logic to rely on I3C device information that could be changed behind their + * back. + */ +static void i3c_bus_maintenance_lock(struct i3c_bus *bus) +{ + down_write(&bus->lock); +} + +/** + * i3c_bus_maintenance_unlock - Release the bus lock after a maintenance + * operation + * @bus: I3C bus to release the lock on + * + * Should be called when the bus maintenance operation is done. See + * i3c_bus_maintenance_lock() for more details on what these maintenance + * operations are. + */ +static void i3c_bus_maintenance_unlock(struct i3c_bus *bus) +{ + up_write(&bus->lock); +} + +/** + * i3c_bus_normaluse_lock - Lock the bus for a normal operation + * @bus: I3C bus to take the lock on + * + * This function takes the bus lock for any operation that is not a maintenance + * operation (see i3c_bus_maintenance_lock() for a non-exhaustive list of + * maintenance operations). Basically all communications with I3C devices are + * normal operations (HDR, SDR transfers or CCC commands that do not change bus + * state or I3C dynamic address). + * + * Note that this lock is not guaranteeing serialization of normal operations. + * In other words, transfer requests passed to the I3C master can be submitted + * in parallel and I3C master drivers have to use their own locking to make + * sure two different communications are not inter-mixed, or access to the + * output/input queue is not done while the engine is busy. + */ +void i3c_bus_normaluse_lock(struct i3c_bus *bus) +{ + down_read(&bus->lock); +} + +/** + * i3c_bus_normaluse_unlock - Release the bus lock after a normal operation + * @bus: I3C bus to release the lock on + * + * Should be called when a normal operation is done. See + * i3c_bus_normaluse_lock() for more details on what these normal operations + * are. + */ +void i3c_bus_normaluse_unlock(struct i3c_bus *bus) +{ + up_read(&bus->lock); +} + +static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev) +{ + return container_of(dev, struct i3c_master_controller, dev); +} + +static const struct device_type i3c_device_type; + +static struct i3c_bus *dev_to_i3cbus(struct device *dev) +{ + struct i3c_master_controller *master; + + if (dev->type == &i3c_device_type) + return dev_to_i3cdev(dev)->bus; + + master = dev_to_i3cmaster(dev); + + return &master->bus; +} + +static struct i3c_dev_desc *dev_to_i3cdesc(struct device *dev) +{ + struct i3c_master_controller *master; + + if (dev->type == &i3c_device_type) + return dev_to_i3cdev(dev)->desc; + + master = container_of(dev, struct i3c_master_controller, dev); + + return master->this; +} + +static ssize_t bcr_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *bus = dev_to_i3cbus(dev); + struct i3c_dev_desc *desc; + ssize_t ret; + + i3c_bus_normaluse_lock(bus); + desc = dev_to_i3cdesc(dev); + ret = sprintf(buf, "%x\n", desc->info.bcr); + i3c_bus_normaluse_unlock(bus); + + return ret; +} +static DEVICE_ATTR_RO(bcr); + +static ssize_t dcr_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *bus = dev_to_i3cbus(dev); + struct i3c_dev_desc *desc; + ssize_t ret; + + i3c_bus_normaluse_lock(bus); + desc = dev_to_i3cdesc(dev); + ret = sprintf(buf, "%x\n", desc->info.dcr); + i3c_bus_normaluse_unlock(bus); + + return ret; +} +static DEVICE_ATTR_RO(dcr); + +static ssize_t pid_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *bus = dev_to_i3cbus(dev); + struct i3c_dev_desc *desc; + ssize_t ret; + + i3c_bus_normaluse_lock(bus); + desc = dev_to_i3cdesc(dev); + ret = sprintf(buf, "%llx\n", desc->info.pid); + i3c_bus_normaluse_unlock(bus); + + return ret; +} +static DEVICE_ATTR_RO(pid); + +static ssize_t dynamic_address_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *bus = dev_to_i3cbus(dev); + struct i3c_dev_desc *desc; + ssize_t ret; + + i3c_bus_normaluse_lock(bus); + desc = dev_to_i3cdesc(dev); + ret = sprintf(buf, "%02x\n", desc->info.dyn_addr); + i3c_bus_normaluse_unlock(bus); + + return ret; +} +static DEVICE_ATTR_RO(dynamic_address); + +static const char * const hdrcap_strings[] = { + "hdr-ddr", "hdr-tsp", "hdr-tsl", +}; + +static ssize_t hdrcap_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *bus = dev_to_i3cbus(dev); + struct i3c_dev_desc *desc; + ssize_t offset = 0, ret; + unsigned long caps; + int mode; + + i3c_bus_normaluse_lock(bus); + desc = dev_to_i3cdesc(dev); + caps = desc->info.hdr_cap; + for_each_set_bit(mode, &caps, 8) { + if (mode >= ARRAY_SIZE(hdrcap_strings)) + break; + + if (!hdrcap_strings[mode]) + continue; + + ret = sprintf(buf + offset, offset ? " %s" : "%s", + hdrcap_strings[mode]); + if (ret < 0) + goto out; + + offset += ret; + } + + ret = sprintf(buf + offset, "\n"); + if (ret < 0) + goto out; + + ret = offset + ret; + +out: + i3c_bus_normaluse_unlock(bus); + + return ret; +} +static DEVICE_ATTR_RO(hdrcap); + +static struct attribute *i3c_device_attrs[] = { + &dev_attr_bcr.attr, + &dev_attr_dcr.attr, + &dev_attr_pid.attr, + &dev_attr_dynamic_address.attr, + &dev_attr_hdrcap.attr, + NULL, +}; +ATTRIBUTE_GROUPS(i3c_device); + +static int i3c_device_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct i3c_device *i3cdev = dev_to_i3cdev(dev); + struct i3c_device_info devinfo; + u16 manuf, part, ext; + + i3c_device_get_info(i3cdev, &devinfo); + manuf = I3C_PID_MANUF_ID(devinfo.pid); + part = I3C_PID_PART_ID(devinfo.pid); + ext = I3C_PID_EXTRA_INFO(devinfo.pid); + + if (I3C_PID_RND_LOWER_32BITS(devinfo.pid)) + return add_uevent_var(env, "MODALIAS=i3c:dcr%02Xmanuf%04X", + devinfo.dcr, manuf); + + return add_uevent_var(env, + "MODALIAS=i3c:dcr%02Xmanuf%04Xpart%04xext%04x", + devinfo.dcr, manuf, part, ext); +} + +static const struct device_type i3c_device_type = { + .groups = i3c_device_groups, + .uevent = i3c_device_uevent, +}; + +static 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; + + i3c_device_get_info(i3cdev, &devinfo); + + /* + * The lower 32bits of the provisional ID is just filled with a random + * value, try to match using DCR info. + */ + if (!I3C_PID_RND_LOWER_32BITS(devinfo.pid)) { + u16 manuf = I3C_PID_MANUF_ID(devinfo.pid); + u16 part = I3C_PID_PART_ID(devinfo.pid); + u16 ext_info = I3C_PID_EXTRA_INFO(devinfo.pid); + + /* First try to match by manufacturer/part ID. */ + for (id = id_table; id->match_flags != 0; id++) { + if ((id->match_flags & I3C_MATCH_MANUF_AND_PART) != + I3C_MATCH_MANUF_AND_PART) + continue; + + if (manuf != id->manuf_id || part != id->part_id) + continue; + + if ((id->match_flags & I3C_MATCH_EXTRA_INFO) && + ext_info != id->extra_info) + continue; + + return id; + } + } + + /* Fallback to DCR match. */ + for (id = id_table; id->match_flags != 0; id++) { + if ((id->match_flags & I3C_MATCH_DCR) && + id->dcr == devinfo.dcr) + return id; + } + + return NULL; +} + +static int i3c_device_match(struct device *dev, struct device_driver *drv) +{ + struct i3c_device *i3cdev; + struct i3c_driver *i3cdrv; + + if (dev->type != &i3c_device_type) + return 0; + + i3cdev = dev_to_i3cdev(dev); + i3cdrv = drv_to_i3cdrv(drv); + if (i3c_device_match_id(i3cdev, i3cdrv->id_table)) + return 1; + + return 0; +} + +static int i3c_device_probe(struct device *dev) +{ + struct i3c_device *i3cdev = dev_to_i3cdev(dev); + struct i3c_driver *driver = drv_to_i3cdrv(dev->driver); + + return driver->probe(i3cdev); +} + +static int i3c_device_remove(struct device *dev) +{ + struct i3c_device *i3cdev = dev_to_i3cdev(dev); + struct i3c_driver *driver = drv_to_i3cdrv(dev->driver); + int ret; + + ret = driver->remove(i3cdev); + if (ret) + return ret; + + i3c_device_free_ibi(i3cdev); + + return ret; +} + +struct bus_type i3c_bus_type = { + .name = "i3c", + .match = i3c_device_match, + .probe = i3c_device_probe, + .remove = i3c_device_remove, +}; + +static enum i3c_addr_slot_status +i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) +{ + int status, bitpos = addr * 2; + + if (addr > I2C_MAX_ADDR) + return I3C_ADDR_SLOT_RSVD; + + status = bus->addrslots[bitpos / BITS_PER_LONG]; + status >>= bitpos % BITS_PER_LONG; + + return status & I3C_ADDR_SLOT_STATUS_MASK; +} + +static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status) +{ + int bitpos = addr * 2; + unsigned long *ptr; + + if (addr > I2C_MAX_ADDR) + return; + + ptr = bus->addrslots + (bitpos / BITS_PER_LONG); + *ptr &= ~(I3C_ADDR_SLOT_STATUS_MASK << (bitpos % BITS_PER_LONG)); + *ptr |= status << (bitpos % BITS_PER_LONG); +} + +static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) +{ + enum i3c_addr_slot_status status; + + status = i3c_bus_get_addr_slot_status(bus, addr); + + return status == I3C_ADDR_SLOT_FREE; +} + +static int i3c_bus_get_free_addr(struct i3c_bus *bus, u8 start_addr) +{ + enum i3c_addr_slot_status status; + u8 addr; + + for (addr = start_addr; addr < I3C_MAX_ADDR; addr++) { + status = i3c_bus_get_addr_slot_status(bus, addr); + if (status == I3C_ADDR_SLOT_FREE) + return addr; + } + + return -ENOMEM; +} + +static void i3c_bus_init_addrslots(struct i3c_bus *bus) +{ + int i; + + /* Addresses 0 to 7 are reserved. */ + for (i = 0; i < 8; i++) + i3c_bus_set_addr_slot_status(bus, i, I3C_ADDR_SLOT_RSVD); + + /* + * Reserve broadcast address and all addresses that might collide + * with the broadcast address when facing a single bit error. + */ + i3c_bus_set_addr_slot_status(bus, I3C_BROADCAST_ADDR, + I3C_ADDR_SLOT_RSVD); + for (i = 0; i < 7; i++) + i3c_bus_set_addr_slot_status(bus, I3C_BROADCAST_ADDR ^ BIT(i), + I3C_ADDR_SLOT_RSVD); +} + +static void i3c_bus_cleanup(struct i3c_bus *i3cbus) +{ + mutex_lock(&i3c_core_lock); + idr_remove(&i3c_bus_idr, i3cbus->id); + mutex_unlock(&i3c_core_lock); +} + +static int i3c_bus_init(struct i3c_bus *i3cbus) +{ + int ret; + + init_rwsem(&i3cbus->lock); + INIT_LIST_HEAD(&i3cbus->devs.i2c); + INIT_LIST_HEAD(&i3cbus->devs.i3c); + i3c_bus_init_addrslots(i3cbus); + i3cbus->mode = I3C_BUS_MODE_PURE; + + mutex_lock(&i3c_core_lock); + ret = idr_alloc(&i3c_bus_idr, i3cbus, 0, 0, GFP_KERNEL); + mutex_unlock(&i3c_core_lock); + + if (ret < 0) + return ret; + + i3cbus->id = ret; + + return 0; +} + +static const char * const i3c_bus_mode_strings[] = { + [I3C_BUS_MODE_PURE] = "pure", + [I3C_BUS_MODE_MIXED_FAST] = "mixed-fast", + [I3C_BUS_MODE_MIXED_SLOW] = "mixed-slow", +}; + +static ssize_t mode_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *i3cbus = dev_to_i3cbus(dev); + ssize_t ret; + + i3c_bus_normaluse_lock(i3cbus); + if (i3cbus->mode < 0 || + i3cbus->mode >= ARRAY_SIZE(i3c_bus_mode_strings) || + !i3c_bus_mode_strings[i3cbus->mode]) + ret = sprintf(buf, "unknown\n"); + else + ret = sprintf(buf, "%s\n", i3c_bus_mode_strings[i3cbus->mode]); + i3c_bus_normaluse_unlock(i3cbus); + + return ret; +} +static DEVICE_ATTR_RO(mode); + +static ssize_t current_master_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *i3cbus = dev_to_i3cbus(dev); + ssize_t ret; + + i3c_bus_normaluse_lock(i3cbus); + ret = sprintf(buf, "%d-%llx\n", i3cbus->id, + i3cbus->cur_master->info.pid); + i3c_bus_normaluse_unlock(i3cbus); + + return ret; +} +static DEVICE_ATTR_RO(current_master); + +static ssize_t i3c_scl_frequency_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *i3cbus = dev_to_i3cbus(dev); + ssize_t ret; + + i3c_bus_normaluse_lock(i3cbus); + ret = sprintf(buf, "%ld\n", i3cbus->scl_rate.i3c); + i3c_bus_normaluse_unlock(i3cbus); + + return ret; +} +static DEVICE_ATTR_RO(i3c_scl_frequency); + +static ssize_t i2c_scl_frequency_show(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct i3c_bus *i3cbus = dev_to_i3cbus(dev); + ssize_t ret; + + i3c_bus_normaluse_lock(i3cbus); + ret = sprintf(buf, "%ld\n", i3cbus->scl_rate.i2c); + i3c_bus_normaluse_unlock(i3cbus); + + return ret; +} +static DEVICE_ATTR_RO(i2c_scl_frequency); + +static struct attribute *i3c_masterdev_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_current_master.attr, + &dev_attr_i3c_scl_frequency.attr, + &dev_attr_i2c_scl_frequency.attr, + &dev_attr_bcr.attr, + &dev_attr_dcr.attr, + &dev_attr_pid.attr, + &dev_attr_dynamic_address.attr, + &dev_attr_hdrcap.attr, + NULL, +}; +ATTRIBUTE_GROUPS(i3c_masterdev); + +static void i3c_masterdev_release(struct device *dev) +{ + struct i3c_master_controller *master = dev_to_i3cmaster(dev); + struct i3c_bus *bus = dev_to_i3cbus(dev); + + if (master->wq) + destroy_workqueue(master->wq); + + WARN_ON(!list_empty(&bus->devs.i2c) || !list_empty(&bus->devs.i3c)); + i3c_bus_cleanup(bus); + + of_node_put(dev->of_node); +} + +static const struct device_type i3c_masterdev_type = { + .groups = i3c_masterdev_groups, +}; + +int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode) +{ + i3cbus->mode = mode; + + if (!i3cbus->scl_rate.i3c) + i3cbus->scl_rate.i3c = I3C_BUS_TYP_I3C_SCL_RATE; + + if (!i3cbus->scl_rate.i2c) { + if (i3cbus->mode == I3C_BUS_MODE_MIXED_SLOW) + i3cbus->scl_rate.i2c = I3C_BUS_I2C_FM_SCL_RATE; + else + i3cbus->scl_rate.i2c = I3C_BUS_I2C_FM_PLUS_SCL_RATE; + } + + /* + * I3C/I2C frequency may have been overridden, check that user-provided + * values are not exceeding max possible frequency. + */ + if (i3cbus->scl_rate.i3c > I3C_BUS_MAX_I3C_SCL_RATE || + i3cbus->scl_rate.i2c > I3C_BUS_I2C_FM_PLUS_SCL_RATE) + return -EINVAL; + + return 0; +} + +static struct i3c_master_controller * +i2c_adapter_to_i3c_master(struct i2c_adapter *adap) +{ + return container_of(adap, struct i3c_master_controller, i2c); +} + +static struct i2c_adapter * +i3c_master_to_i2c_adapter(struct i3c_master_controller *master) +{ + return &master->i2c; +} + +static void i3c_master_free_i2c_dev(struct i2c_dev_desc *dev) +{ + kfree(dev); +} + +static struct i2c_dev_desc * +i3c_master_alloc_i2c_dev(struct i3c_master_controller *master, + const struct i2c_dev_boardinfo *boardinfo) +{ + struct i2c_dev_desc *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return ERR_PTR(-ENOMEM); + + dev->common.master = master; + dev->boardinfo = boardinfo; + + return dev; +} + +static void *i3c_ccc_cmd_dest_init(struct i3c_ccc_cmd_dest *dest, u8 addr, + u16 payloadlen) +{ + dest->addr = addr; + dest->payload.len = payloadlen; + if (payloadlen) + dest->payload.data = kzalloc(payloadlen, GFP_KERNEL); + else + dest->payload.data = NULL; + + return dest->payload.data; +} + +static void i3c_ccc_cmd_dest_cleanup(struct i3c_ccc_cmd_dest *dest) +{ + kfree(dest->payload.data); +} + +static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id, + struct i3c_ccc_cmd_dest *dests, + unsigned int ndests) +{ + cmd->rnw = rnw ? 1 : 0; + cmd->id = id; + cmd->dests = dests; + cmd->ndests = ndests; + cmd->err = I3C_ERROR_UNKNOWN; +} + +static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master, + struct i3c_ccc_cmd *cmd) +{ + int ret; + + if (!cmd || !master) + return -EINVAL; + + if (WARN_ON(master->init_done && + !rwsem_is_locked(&master->bus.lock))) + return -EINVAL; + + if (!master->ops->send_ccc_cmd) + return -ENOTSUPP; + + if ((cmd->id & I3C_CCC_DIRECT) && (!cmd->dests || !cmd->ndests)) + return -EINVAL; + + if (master->ops->supports_ccc_cmd && + !master->ops->supports_ccc_cmd(master, cmd)) + return -ENOTSUPP; + + ret = master->ops->send_ccc_cmd(master, cmd); + if (ret) { + if (cmd->err != I3C_ERROR_UNKNOWN) + return cmd->err; + + return ret; + } + + return 0; +} + +static struct i2c_dev_desc * +i3c_master_find_i2c_dev_by_addr(const struct i3c_master_controller *master, + u16 addr) +{ + struct i2c_dev_desc *dev; + + i3c_bus_for_each_i2cdev(&master->bus, dev) { + if (dev->boardinfo->base.addr == addr) + return dev; + } + + return NULL; +} + +/** + * i3c_master_get_free_addr() - get a free address on the bus + * @master: I3C master object + * @start_addr: where to start searching + * + * This function must be called with the bus lock held in w |