| // 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 |
| * @aliases: Array of aliases, must hold exactly @size elements |
| * @use_mask: Mask of used aliases |
| */ |
| struct i2c_atr_alias_pool { |
| size_t size; |
| bool shared; |
| |
| /* Protects aliases and use_mask */ |
| spinlock_t lock; |
| u16 *aliases; |
| unsigned long *use_mask; |
| }; |
| |
| /** |
| * 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(sizeof(*alias_pool), GFP_KERNEL); |
| if (!alias_pool) |
| return ERR_PTR(-ENOMEM); |
| |
| alias_pool->size = num_aliases; |
| |
| alias_pool->aliases = kcalloc(num_aliases, sizeof(*alias_pool->aliases), GFP_KERNEL); |
| if (!alias_pool->aliases) { |
| ret = -ENOMEM; |
| goto err_free_alias_pool; |
| } |
| |
| alias_pool->use_mask = bitmap_zalloc(num_aliases, GFP_KERNEL); |
| if (!alias_pool->use_mask) { |
| ret = -ENOMEM; |
| goto err_free_aliases; |
| } |
| |
| alias_pool->shared = shared; |
| |
| spin_lock_init(&alias_pool->lock); |
| |
| return alias_pool; |
| |
| err_free_aliases: |
| kfree(alias_pool->aliases); |
| 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->aliases); |
| 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(sizeof(*c2a), GFP_KERNEL); |
| 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; |
| |
| 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); |
| 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; |
| |
| 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); |
| 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); |
| } |
| |
| 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(struct_size(atr, adapter, max_adapters), GFP_KERNEL); |
| 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(sizeof(*chan), GFP_KERNEL); |
| 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) { |
| 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"); |