| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * I2C Address Translator | |
| * | |
| * Copyright (c) 2019,2022 Luca Ceresoli <luca@lucaceresoli.net> | |
| * Copyright (c) 2022,2023 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> | |
| * | |
| * Originally based on i2c-mux.c | |
| */ | |
| #include <linux/i2c-atr.h> | |
| #include <linux/i2c.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/property.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/lockdep.h> | |
| #define ATR_MAX_ADAPTERS 100 /* Just a sanity limit */ | |
| #define ATR_MAX_SYMLINK_LEN 11 /* Longest name is 10 chars: "channel-99" */ | |
| /** | |
| * struct i2c_atr_alias_pair - Holds the alias assigned to a client address. | |
| * @node: List node | |
| * @addr: Address of the client on the child bus. | |
| * @alias: I2C alias address assigned by the driver. | |
| * This is the address that will be used to issue I2C transactions | |
| * on the parent (physical) bus. | |
| * @fixed: Alias pair cannot be replaced during dynamic address attachment. | |
| * This flag is necessary for situations where a single I2C transaction | |
| * contains more distinct target addresses than the ATR channel can handle. | |
| * It marks addresses that have already been attached to an alias so | |
| * that their alias pair is not evicted by a subsequent address in the same | |
| * transaction. | |
| * | |
| */ | |
| struct i2c_atr_alias_pair { | |
| struct list_head node; | |
| bool fixed; | |
| u16 addr; | |
| u16 alias; | |
| }; | |
| /** | |
| * struct i2c_atr_alias_pool - Pool of client aliases available for an ATR. | |
| * @size: Total number of aliases | |
| * @shared: Indicates if this alias pool is shared by multiple channels | |
| * | |
| * @lock: Lock protecting @aliases and @use_mask | |
| * @use_mask: Mask of used aliases | |
| * @aliases: Array of aliases, must hold exactly @size elements | |
| */ | |
| struct i2c_atr_alias_pool { | |
| size_t size; | |
| bool shared; | |
| /* Protects aliases and use_mask */ | |
| spinlock_t lock; | |
| unsigned long *use_mask; | |
| u16 aliases[] __counted_by(size); | |
| }; | |
| /** | |
| * struct i2c_atr_chan - Data for a channel. | |
| * @adap: The &struct i2c_adapter for the channel | |
| * @atr: The parent I2C ATR | |
| * @chan_id: The ID of this channel | |
| * @alias_pairs_lock: Mutex protecting @alias_pairs | |
| * @alias_pairs_lock_key: Lock key for @alias_pairs_lock | |
| * @alias_pairs: List of @struct i2c_atr_alias_pair containing the | |
| * assigned aliases | |
| * @alias_pool: Pool of available client aliases | |
| * | |
| * @orig_addrs_lock: Mutex protecting @orig_addrs | |
| * @orig_addrs_lock_key: Lock key for @orig_addrs_lock | |
| * @orig_addrs: Buffer used to store the original addresses during transmit | |
| * @orig_addrs_size: Size of @orig_addrs | |
| */ | |
| struct i2c_atr_chan { | |
| struct i2c_adapter adap; | |
| struct i2c_atr *atr; | |
| u32 chan_id; | |
| /* Lock alias_pairs during attach/detach */ | |
| struct mutex alias_pairs_lock; | |
| struct lock_class_key alias_pairs_lock_key; | |
| struct list_head alias_pairs; | |
| struct i2c_atr_alias_pool *alias_pool; | |
| /* Lock orig_addrs during xfer */ | |
| struct mutex orig_addrs_lock; | |
| struct lock_class_key orig_addrs_lock_key; | |
| u16 *orig_addrs; | |
| unsigned int orig_addrs_size; | |
| }; | |
| /** | |
| * struct i2c_atr - The I2C ATR instance | |
| * @parent: The parent &struct i2c_adapter | |
| * @dev: The device that owns the I2C ATR instance | |
| * @ops: &struct i2c_atr_ops | |
| * @priv: Private driver data, set with i2c_atr_set_driver_data() | |
| * @algo: The &struct i2c_algorithm for adapters | |
| * @lock: Lock for the I2C bus segment (see &struct i2c_lock_operations) | |
| * @lock_key: Lock key for @lock | |
| * @max_adapters: Maximum number of adapters this I2C ATR can have | |
| * @flags: Flags for ATR | |
| * @alias_pool: Optional common pool of available client aliases | |
| * @i2c_nb: Notifier for remote client add & del events | |
| * @adapter: Array of adapters | |
| */ | |
| struct i2c_atr { | |
| struct i2c_adapter *parent; | |
| struct device *dev; | |
| const struct i2c_atr_ops *ops; | |
| void *priv; | |
| struct i2c_algorithm algo; | |
| /* lock for the I2C bus segment (see struct i2c_lock_operations) */ | |
| struct mutex lock; | |
| struct lock_class_key lock_key; | |
| int max_adapters; | |
| u32 flags; | |
| struct i2c_atr_alias_pool *alias_pool; | |
| struct notifier_block i2c_nb; | |
| struct i2c_adapter *adapter[] __counted_by(max_adapters); | |
| }; | |
| static struct i2c_atr_alias_pool *i2c_atr_alloc_alias_pool(size_t num_aliases, bool shared) | |
| { | |
| struct i2c_atr_alias_pool *alias_pool; | |
| int ret; | |
| alias_pool = kzalloc_flex(*alias_pool, aliases, num_aliases); | |
| if (!alias_pool) | |
| return ERR_PTR(-ENOMEM); | |
| alias_pool->size = num_aliases; | |
| alias_pool->use_mask = bitmap_zalloc(num_aliases, GFP_KERNEL); | |
| if (!alias_pool->use_mask) { | |
| ret = -ENOMEM; | |
| goto err_free_alias_pool; | |
| } | |
| alias_pool->shared = shared; | |
| spin_lock_init(&alias_pool->lock); | |
| return alias_pool; | |
| err_free_alias_pool: | |
| kfree(alias_pool); | |
| return ERR_PTR(ret); | |
| } | |
| static void i2c_atr_free_alias_pool(struct i2c_atr_alias_pool *alias_pool) | |
| { | |
| bitmap_free(alias_pool->use_mask); | |
| kfree(alias_pool); | |
| } | |
| /* Must be called with alias_pairs_lock held */ | |
| static struct i2c_atr_alias_pair *i2c_atr_create_c2a(struct i2c_atr_chan *chan, | |
| u16 alias, u16 addr) | |
| { | |
| struct i2c_atr_alias_pair *c2a; | |
| lockdep_assert_held(&chan->alias_pairs_lock); | |
| c2a = kzalloc_obj(*c2a); | |
| if (!c2a) | |
| return NULL; | |
| c2a->addr = addr; | |
| c2a->alias = alias; | |
| list_add(&c2a->node, &chan->alias_pairs); | |
| return c2a; | |
| } | |
| /* Must be called with alias_pairs_lock held */ | |
| static void i2c_atr_destroy_c2a(struct i2c_atr_alias_pair **pc2a) | |
| { | |
| list_del(&(*pc2a)->node); | |
| kfree(*pc2a); | |
| *pc2a = NULL; | |
| } | |
| static int i2c_atr_reserve_alias(struct i2c_atr_alias_pool *alias_pool) | |
| { | |
| unsigned long idx; | |
| u16 alias; | |
| spin_lock(&alias_pool->lock); | |
| idx = find_first_zero_bit(alias_pool->use_mask, alias_pool->size); | |
| if (idx >= alias_pool->size) { | |
| spin_unlock(&alias_pool->lock); | |
| return -EBUSY; | |
| } | |
| set_bit(idx, alias_pool->use_mask); | |
| alias = alias_pool->aliases[idx]; | |
| spin_unlock(&alias_pool->lock); | |
| return alias; | |
| } | |
| static void i2c_atr_release_alias(struct i2c_atr_alias_pool *alias_pool, u16 alias) | |
| { | |
| unsigned int idx; | |
| spin_lock(&alias_pool->lock); | |
| for (idx = 0; idx < alias_pool->size; ++idx) { | |
| if (alias_pool->aliases[idx] == alias) { | |
| clear_bit(idx, alias_pool->use_mask); | |
| spin_unlock(&alias_pool->lock); | |
| return; | |
| } | |
| } | |
| spin_unlock(&alias_pool->lock); | |
| } | |
| static struct i2c_atr_alias_pair * | |
| i2c_atr_find_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr) | |
| { | |
| struct i2c_atr_alias_pair *c2a; | |
| lockdep_assert_held(&chan->alias_pairs_lock); | |
| list_for_each_entry(c2a, &chan->alias_pairs, node) { | |
| if (c2a->addr == addr) | |
| return c2a; | |
| } | |
| return NULL; | |
| } | |
| static struct i2c_atr_alias_pair * | |
| i2c_atr_create_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr) | |
| { | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_atr_alias_pair *c2a; | |
| u16 alias; | |
| int ret; | |
| lockdep_assert_held(&chan->alias_pairs_lock); | |
| ret = i2c_atr_reserve_alias(chan->alias_pool); | |
| if (ret < 0) | |
| return NULL; | |
| alias = ret; | |
| c2a = i2c_atr_create_c2a(chan, alias, addr); | |
| if (!c2a) | |
| goto err_release_alias; | |
| ret = atr->ops->attach_addr(atr, chan->chan_id, c2a->addr, c2a->alias); | |
| if (ret) { | |
| dev_err(atr->dev, "failed to attach 0x%02x on channel %d: err %d\n", | |
| addr, chan->chan_id, ret); | |
| goto err_del_c2a; | |
| } | |
| return c2a; | |
| err_del_c2a: | |
| i2c_atr_destroy_c2a(&c2a); | |
| err_release_alias: | |
| i2c_atr_release_alias(chan->alias_pool, alias); | |
| return NULL; | |
| } | |
| static struct i2c_atr_alias_pair * | |
| i2c_atr_replace_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr) | |
| { | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_atr_alias_pair *c2a; | |
| struct list_head *alias_pairs; | |
| bool found = false; | |
| u16 alias; | |
| int ret; | |
| lockdep_assert_held(&chan->alias_pairs_lock); | |
| alias_pairs = &chan->alias_pairs; | |
| if (unlikely(list_empty(alias_pairs))) | |
| return NULL; | |
| list_for_each_entry_reverse(c2a, alias_pairs, node) { | |
| if (!c2a->fixed) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) | |
| return NULL; | |
| atr->ops->detach_addr(atr, chan->chan_id, c2a->addr); | |
| c2a->addr = addr; | |
| list_move(&c2a->node, alias_pairs); | |
| alias = c2a->alias; | |
| ret = atr->ops->attach_addr(atr, chan->chan_id, c2a->addr, c2a->alias); | |
| if (ret) { | |
| dev_err(atr->dev, "failed to attach 0x%02x on channel %d: err %d\n", | |
| addr, chan->chan_id, ret); | |
| i2c_atr_destroy_c2a(&c2a); | |
| i2c_atr_release_alias(chan->alias_pool, alias); | |
| return NULL; | |
| } | |
| return c2a; | |
| } | |
| static struct i2c_atr_alias_pair * | |
| i2c_atr_get_mapping_by_addr(struct i2c_atr_chan *chan, u16 addr) | |
| { | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_atr_alias_pair *c2a; | |
| c2a = i2c_atr_find_mapping_by_addr(chan, addr); | |
| if (c2a) | |
| return c2a; | |
| if (atr->flags & I2C_ATR_F_STATIC) | |
| return NULL; | |
| c2a = i2c_atr_create_mapping_by_addr(chan, addr); | |
| if (c2a) | |
| return c2a; | |
| return i2c_atr_replace_mapping_by_addr(chan, addr); | |
| } | |
| /* | |
| * Replace all message addresses with their aliases, saving the original | |
| * addresses. | |
| * | |
| * This function is internal for use in i2c_atr_master_xfer(). It must be | |
| * followed by i2c_atr_unmap_msgs() to restore the original addresses. | |
| */ | |
| static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, | |
| int num) | |
| { | |
| struct i2c_atr *atr = chan->atr; | |
| static struct i2c_atr_alias_pair *c2a; | |
| int i, ret = 0; | |
| /* Ensure we have enough room to save the original addresses */ | |
| if (unlikely(chan->orig_addrs_size < num)) { | |
| u16 *new_buf; | |
| /* We don't care about old data, hence no realloc() */ | |
| new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL); | |
| if (!new_buf) | |
| return -ENOMEM; | |
| kfree(chan->orig_addrs); | |
| chan->orig_addrs = new_buf; | |
| chan->orig_addrs_size = num; | |
| } | |
| mutex_lock(&chan->alias_pairs_lock); | |
| for (i = 0; i < num; i++) { | |
| chan->orig_addrs[i] = msgs[i].addr; | |
| c2a = i2c_atr_get_mapping_by_addr(chan, msgs[i].addr); | |
| if (!c2a) { | |
| if (atr->flags & I2C_ATR_F_PASSTHROUGH) | |
| continue; | |
| dev_err(atr->dev, "client 0x%02x not mapped!\n", | |
| msgs[i].addr); | |
| while (i--) | |
| msgs[i].addr = chan->orig_addrs[i]; | |
| ret = -ENXIO; | |
| goto out_unlock; | |
| } | |
| // Prevent c2a from being overwritten by another client in this transaction | |
| c2a->fixed = true; | |
| msgs[i].addr = c2a->alias; | |
| } | |
| out_unlock: | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| return ret; | |
| } | |
| /* | |
| * Restore all message address aliases with the original addresses. This | |
| * function is internal for use in i2c_atr_master_xfer() and for this reason it | |
| * needs no null and size checks on orig_addr. | |
| * | |
| * @see i2c_atr_map_msgs() | |
| */ | |
| static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, | |
| int num) | |
| { | |
| struct i2c_atr_alias_pair *c2a; | |
| int i; | |
| for (i = 0; i < num; i++) | |
| msgs[i].addr = chan->orig_addrs[i]; | |
| mutex_lock(&chan->alias_pairs_lock); | |
| if (unlikely(list_empty(&chan->alias_pairs))) | |
| goto out_unlock; | |
| // unfix c2a entries so that subsequent transfers can reuse their aliases | |
| list_for_each_entry(c2a, &chan->alias_pairs, node) { | |
| c2a->fixed = false; | |
| } | |
| out_unlock: | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| } | |
| static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, | |
| int num) | |
| { | |
| struct i2c_atr_chan *chan = adap->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_adapter *parent = atr->parent; | |
| int ret; | |
| /* Translate addresses */ | |
| mutex_lock(&chan->orig_addrs_lock); | |
| ret = i2c_atr_map_msgs(chan, msgs, num); | |
| if (ret < 0) | |
| goto err_unlock; | |
| /* Perform the transfer */ | |
| ret = i2c_transfer(parent, msgs, num); | |
| /* Restore addresses */ | |
| i2c_atr_unmap_msgs(chan, msgs, num); | |
| err_unlock: | |
| mutex_unlock(&chan->orig_addrs_lock); | |
| return ret; | |
| } | |
| static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr, | |
| unsigned short flags, char read_write, u8 command, | |
| int size, union i2c_smbus_data *data) | |
| { | |
| struct i2c_atr_chan *chan = adap->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_adapter *parent = atr->parent; | |
| struct i2c_atr_alias_pair *c2a; | |
| u16 alias; | |
| mutex_lock(&chan->alias_pairs_lock); | |
| c2a = i2c_atr_get_mapping_by_addr(chan, addr); | |
| if (!c2a && !(atr->flags & I2C_ATR_F_PASSTHROUGH)) { | |
| dev_err(atr->dev, "client 0x%02x not mapped!\n", addr); | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| return -ENXIO; | |
| } | |
| alias = c2a ? c2a->alias : addr; | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| return i2c_smbus_xfer(parent, alias, flags, read_write, command, | |
| size, data); | |
| } | |
| static u32 i2c_atr_functionality(struct i2c_adapter *adap) | |
| { | |
| struct i2c_atr_chan *chan = adap->algo_data; | |
| struct i2c_adapter *parent = chan->atr->parent; | |
| return parent->algo->functionality(parent); | |
| } | |
| static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int flags) | |
| { | |
| struct i2c_atr_chan *chan = adapter->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| mutex_lock(&atr->lock); | |
| } | |
| static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int flags) | |
| { | |
| struct i2c_atr_chan *chan = adapter->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| return mutex_trylock(&atr->lock); | |
| } | |
| static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int flags) | |
| { | |
| struct i2c_atr_chan *chan = adapter->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| mutex_unlock(&atr->lock); | |
| } | |
| static const struct i2c_lock_operations i2c_atr_lock_ops = { | |
| .lock_bus = i2c_atr_lock_bus, | |
| .trylock_bus = i2c_atr_trylock_bus, | |
| .unlock_bus = i2c_atr_unlock_bus, | |
| }; | |
| static int i2c_atr_attach_addr(struct i2c_adapter *adapter, | |
| u16 addr) | |
| { | |
| struct i2c_atr_chan *chan = adapter->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_atr_alias_pair *c2a; | |
| int ret = 0; | |
| /* | |
| * attach_addr() may reconfigure the remote side of the bus, keep | |
| * transfers on other channels out until it is done. | |
| */ | |
| mutex_lock(&atr->lock); | |
| mutex_lock(&chan->alias_pairs_lock); | |
| c2a = i2c_atr_create_mapping_by_addr(chan, addr); | |
| if (!c2a && !(atr->flags & I2C_ATR_F_STATIC)) | |
| c2a = i2c_atr_replace_mapping_by_addr(chan, addr); | |
| if (!c2a) { | |
| dev_err(atr->dev, "failed to find a free alias\n"); | |
| ret = -EBUSY; | |
| goto out_unlock; | |
| } | |
| dev_dbg(atr->dev, "chan%u: using alias 0x%02x for addr 0x%02x\n", | |
| chan->chan_id, c2a->alias, addr); | |
| out_unlock: | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| mutex_unlock(&atr->lock); | |
| return ret; | |
| } | |
| static void i2c_atr_detach_addr(struct i2c_adapter *adapter, | |
| u16 addr) | |
| { | |
| struct i2c_atr_chan *chan = adapter->algo_data; | |
| struct i2c_atr *atr = chan->atr; | |
| struct i2c_atr_alias_pair *c2a; | |
| mutex_lock(&atr->lock); | |
| atr->ops->detach_addr(atr, chan->chan_id, addr); | |
| mutex_lock(&chan->alias_pairs_lock); | |
| c2a = i2c_atr_find_mapping_by_addr(chan, addr); | |
| if (!c2a) { | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| mutex_unlock(&atr->lock); | |
| return; | |
| } | |
| i2c_atr_release_alias(chan->alias_pool, c2a->alias); | |
| dev_dbg(atr->dev, | |
| "chan%u: detached alias 0x%02x from addr 0x%02x\n", | |
| chan->chan_id, c2a->alias, addr); | |
| i2c_atr_destroy_c2a(&c2a); | |
| mutex_unlock(&chan->alias_pairs_lock); | |
| mutex_unlock(&atr->lock); | |
| } | |
| static int i2c_atr_bus_notifier_call(struct notifier_block *nb, | |
| unsigned long event, void *device) | |
| { | |
| struct i2c_atr *atr = container_of(nb, struct i2c_atr, i2c_nb); | |
| struct device *dev = device; | |
| struct i2c_client *client; | |
| u32 chan_id; | |
| int ret; | |
| client = i2c_verify_client(dev); | |
| if (!client) | |
| return NOTIFY_DONE; | |
| /* Is the client in one of our adapters? */ | |
| for (chan_id = 0; chan_id < atr->max_adapters; ++chan_id) { | |
| if (client->adapter == atr->adapter[chan_id]) | |
| break; | |
| } | |
| if (chan_id == atr->max_adapters) | |
| return NOTIFY_DONE; | |
| switch (event) { | |
| case BUS_NOTIFY_ADD_DEVICE: | |
| ret = i2c_atr_attach_addr(client->adapter, client->addr); | |
| if (ret) | |
| dev_err(atr->dev, | |
| "Failed to attach remote client '%s': %d\n", | |
| dev_name(dev), ret); | |
| break; | |
| case BUS_NOTIFY_REMOVED_DEVICE: | |
| i2c_atr_detach_addr(client->adapter, client->addr); | |
| break; | |
| default: | |
| break; | |
| } | |
| return NOTIFY_DONE; | |
| } | |
| static int i2c_atr_parse_alias_pool(struct i2c_atr *atr) | |
| { | |
| struct i2c_atr_alias_pool *alias_pool; | |
| struct device *dev = atr->dev; | |
| size_t num_aliases; | |
| unsigned int i; | |
| u32 *aliases32; | |
| int ret; | |
| if (!fwnode_property_present(dev_fwnode(dev), "i2c-alias-pool")) { | |
| num_aliases = 0; | |
| } else { | |
| ret = fwnode_property_count_u32(dev_fwnode(dev), "i2c-alias-pool"); | |
| if (ret < 0) { | |
| dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| num_aliases = ret; | |
| } | |
| alias_pool = i2c_atr_alloc_alias_pool(num_aliases, true); | |
| if (IS_ERR(alias_pool)) { | |
| ret = PTR_ERR(alias_pool); | |
| dev_err(dev, "Failed to allocate alias pool, err %d\n", ret); | |
| return ret; | |
| } | |
| atr->alias_pool = alias_pool; | |
| if (!alias_pool->size) | |
| return 0; | |
| aliases32 = kcalloc(num_aliases, sizeof(*aliases32), GFP_KERNEL); | |
| if (!aliases32) { | |
| ret = -ENOMEM; | |
| goto err_free_alias_pool; | |
| } | |
| ret = fwnode_property_read_u32_array(dev_fwnode(dev), "i2c-alias-pool", | |
| aliases32, num_aliases); | |
| if (ret < 0) { | |
| dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n", | |
| ret); | |
| goto err_free_aliases32; | |
| } | |
| for (i = 0; i < num_aliases; i++) { | |
| if (!(aliases32[i] & 0xffff0000)) { | |
| alias_pool->aliases[i] = aliases32[i]; | |
| continue; | |
| } | |
| dev_err(dev, "Failed to parse 'i2c-alias-pool' property: I2C flags are not supported\n"); | |
| ret = -EINVAL; | |
| goto err_free_aliases32; | |
| } | |
| kfree(aliases32); | |
| dev_dbg(dev, "i2c-alias-pool has %zu aliases\n", alias_pool->size); | |
| return 0; | |
| err_free_aliases32: | |
| kfree(aliases32); | |
| err_free_alias_pool: | |
| i2c_atr_free_alias_pool(alias_pool); | |
| return ret; | |
| } | |
| struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, | |
| const struct i2c_atr_ops *ops, int max_adapters, | |
| u32 flags) | |
| { | |
| struct i2c_atr *atr; | |
| int ret; | |
| if (max_adapters > ATR_MAX_ADAPTERS) | |
| return ERR_PTR(-EINVAL); | |
| if (!ops || !ops->attach_addr || !ops->detach_addr) | |
| return ERR_PTR(-EINVAL); | |
| atr = kzalloc_flex(*atr, adapter, max_adapters); | |
| if (!atr) | |
| return ERR_PTR(-ENOMEM); | |
| lockdep_register_key(&atr->lock_key); | |
| mutex_init_with_key(&atr->lock, &atr->lock_key); | |
| atr->parent = parent; | |
| atr->dev = dev; | |
| atr->ops = ops; | |
| atr->max_adapters = max_adapters; | |
| atr->flags = flags; | |
| if (parent->algo->master_xfer) | |
| atr->algo.xfer = i2c_atr_master_xfer; | |
| if (parent->algo->smbus_xfer) | |
| atr->algo.smbus_xfer = i2c_atr_smbus_xfer; | |
| atr->algo.functionality = i2c_atr_functionality; | |
| ret = i2c_atr_parse_alias_pool(atr); | |
| if (ret) | |
| goto err_destroy_mutex; | |
| atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call; | |
| ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb); | |
| if (ret) | |
| goto err_free_alias_pool; | |
| return atr; | |
| err_free_alias_pool: | |
| i2c_atr_free_alias_pool(atr->alias_pool); | |
| err_destroy_mutex: | |
| mutex_destroy(&atr->lock); | |
| lockdep_unregister_key(&atr->lock_key); | |
| kfree(atr); | |
| return ERR_PTR(ret); | |
| } | |
| EXPORT_SYMBOL_NS_GPL(i2c_atr_new, "I2C_ATR"); | |
| void i2c_atr_delete(struct i2c_atr *atr) | |
| { | |
| unsigned int i; | |
| for (i = 0; i < atr->max_adapters; ++i) | |
| WARN_ON(atr->adapter[i]); | |
| bus_unregister_notifier(&i2c_bus_type, &atr->i2c_nb); | |
| i2c_atr_free_alias_pool(atr->alias_pool); | |
| mutex_destroy(&atr->lock); | |
| lockdep_unregister_key(&atr->lock_key); | |
| kfree(atr); | |
| } | |
| EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, "I2C_ATR"); | |
| int i2c_atr_add_adapter(struct i2c_atr *atr, struct i2c_atr_adap_desc *desc) | |
| { | |
| struct fwnode_handle *bus_handle = desc->bus_handle; | |
| struct i2c_adapter *parent = atr->parent; | |
| char symlink_name[ATR_MAX_SYMLINK_LEN]; | |
| struct device *dev = atr->dev; | |
| u32 chan_id = desc->chan_id; | |
| struct i2c_atr_chan *chan; | |
| int ret, idx; | |
| if (chan_id >= atr->max_adapters) { | |
| dev_err(dev, "No room for more i2c-atr adapters\n"); | |
| return -EINVAL; | |
| } | |
| if (atr->adapter[chan_id]) { | |
| dev_err(dev, "Adapter %d already present\n", chan_id); | |
| return -EEXIST; | |
| } | |
| chan = kzalloc_obj(*chan); | |
| if (!chan) | |
| return -ENOMEM; | |
| if (!desc->parent) | |
| desc->parent = dev; | |
| chan->atr = atr; | |
| chan->chan_id = chan_id; | |
| INIT_LIST_HEAD(&chan->alias_pairs); | |
| lockdep_register_key(&chan->alias_pairs_lock_key); | |
| lockdep_register_key(&chan->orig_addrs_lock_key); | |
| mutex_init_with_key(&chan->alias_pairs_lock, &chan->alias_pairs_lock_key); | |
| mutex_init_with_key(&chan->orig_addrs_lock, &chan->orig_addrs_lock_key); | |
| snprintf(chan->adap.name, sizeof(chan->adap.name), "i2c-%d-atr-%d", | |
| i2c_adapter_id(parent), chan_id); | |
| chan->adap.owner = THIS_MODULE; | |
| chan->adap.algo = &atr->algo; | |
| chan->adap.algo_data = chan; | |
| chan->adap.dev.parent = desc->parent; | |
| chan->adap.retries = parent->retries; | |
| chan->adap.timeout = parent->timeout; | |
| chan->adap.quirks = parent->quirks; | |
| chan->adap.lock_ops = &i2c_atr_lock_ops; | |
| if (bus_handle) { | |
| device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle)); | |
| } else { | |
| struct fwnode_handle *atr_node; | |
| struct fwnode_handle *child; | |
| u32 reg; | |
| atr_node = device_get_named_child_node(dev, "i2c-atr"); | |
| fwnode_for_each_child_node(atr_node, child) { | |
| ret = fwnode_property_read_u32(child, "reg", ®); | |
| if (ret) | |
| continue; | |
| if (chan_id == reg) | |
| break; | |
| } | |
| device_set_node(&chan->adap.dev, child); | |
| fwnode_handle_put(atr_node); | |
| } | |
| if (desc->num_aliases > 0) { | |
| chan->alias_pool = i2c_atr_alloc_alias_pool(desc->num_aliases, false); | |
| if (IS_ERR(chan->alias_pool)) { | |
| ret = PTR_ERR(chan->alias_pool); | |
| goto err_fwnode_put; | |
| } | |
| for (idx = 0; idx < desc->num_aliases; idx++) | |
| chan->alias_pool->aliases[idx] = desc->aliases[idx]; | |
| } else { | |
| chan->alias_pool = atr->alias_pool; | |
| } | |
| atr->adapter[chan_id] = &chan->adap; | |
| ret = i2c_add_adapter(&chan->adap); | |
| if (ret) { | |
| atr->adapter[chan_id] = NULL; | |
| dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", | |
| chan_id, ret); | |
| goto err_free_alias_pool; | |
| } | |
| snprintf(symlink_name, sizeof(symlink_name), "channel-%u", | |
| chan->chan_id); | |
| ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device"); | |
| if (ret) | |
| dev_warn(dev, "can't create symlink to atr device\n"); | |
| ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name); | |
| if (ret) | |
| dev_warn(dev, "can't create symlink for channel %u\n", chan_id); | |
| dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap)); | |
| return 0; | |
| err_free_alias_pool: | |
| if (!chan->alias_pool->shared) | |
| i2c_atr_free_alias_pool(chan->alias_pool); | |
| err_fwnode_put: | |
| fwnode_handle_put(dev_fwnode(&chan->adap.dev)); | |
| mutex_destroy(&chan->orig_addrs_lock); | |
| mutex_destroy(&chan->alias_pairs_lock); | |
| lockdep_unregister_key(&chan->orig_addrs_lock_key); | |
| lockdep_unregister_key(&chan->alias_pairs_lock_key); | |
| kfree(chan); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(i2c_atr_add_adapter, "I2C_ATR"); | |
| void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id) | |
| { | |
| char symlink_name[ATR_MAX_SYMLINK_LEN]; | |
| struct i2c_adapter *adap; | |
| struct i2c_atr_chan *chan; | |
| struct fwnode_handle *fwnode; | |
| struct device *dev = atr->dev; | |
| adap = atr->adapter[chan_id]; | |
| if (!adap) | |
| return; | |
| chan = adap->algo_data; | |
| fwnode = dev_fwnode(&adap->dev); | |
| dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap)); | |
| snprintf(symlink_name, sizeof(symlink_name), "channel-%u", | |
| chan->chan_id); | |
| sysfs_remove_link(&dev->kobj, symlink_name); | |
| sysfs_remove_link(&chan->adap.dev.kobj, "atr_device"); | |
| i2c_del_adapter(adap); | |
| if (!chan->alias_pool->shared) | |
| i2c_atr_free_alias_pool(chan->alias_pool); | |
| atr->adapter[chan_id] = NULL; | |
| fwnode_handle_put(fwnode); | |
| mutex_destroy(&chan->orig_addrs_lock); | |
| mutex_destroy(&chan->alias_pairs_lock); | |
| lockdep_unregister_key(&chan->orig_addrs_lock_key); | |
| lockdep_unregister_key(&chan->alias_pairs_lock_key); | |
| kfree(chan->orig_addrs); | |
| kfree(chan); | |
| } | |
| EXPORT_SYMBOL_NS_GPL(i2c_atr_del_adapter, "I2C_ATR"); | |
| void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data) | |
| { | |
| atr->priv = data; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, "I2C_ATR"); | |
| void *i2c_atr_get_driver_data(struct i2c_atr *atr) | |
| { | |
| return atr->priv; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, "I2C_ATR"); | |
| MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>"); | |
| MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>"); | |
| MODULE_DESCRIPTION("I2C Address Translator"); | |
| MODULE_LICENSE("GPL"); |