diff --git a/Makefile b/Makefile index 67ed2bde..29d377b8 100644 --- a/Makefile +++ b/Makefile @@ -136,6 +136,7 @@ obj-m += 6.12.0/drivers/media/pci/intel/ endif obj-y += drivers/media/platform/intel/ +obj-m += drivers/i2c/i2c-atr.o obj-m += drivers/media/i2c/ subdir-ccflags-y += $(subdir-ccflags-m) diff --git a/dkms.conf b/dkms.conf index 6afc0b9c..a96dc6ce 100644 --- a/dkms.conf +++ b/dkms.conf @@ -1,5 +1,5 @@ PACKAGE_NAME="ipu-camera-sensor" -PACKAGE_VERSION="0.1" +PACKAGE_VERSION="4.3" VERSION_SUFFIX="${PACKAGE_VERSION}" MAKE="make KERNELRELEASE=$kernelver DRIVER_VERSION_SUFFIX='${VERSION_SUFFIX}' KERNEL_SRC=$kernel_source_dir" @@ -140,6 +140,11 @@ BUILT_MODULE_LOCATION[$i]="drivers/media/i2c" DEST_MODULE_LOCATION[$i]="/updates" STRIP[$i]=no +BUILT_MODULE_NAME[$((++i))]="i2c-atr" +BUILT_MODULE_LOCATION[$i]="drivers/i2c" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + BUILT_MODULE_NAME[$((++i))]="max9x" BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/max9x" DEST_MODULE_LOCATION[$i]="/updates" @@ -174,10 +179,10 @@ STRIP[$i]=no # 6.12 and 6.17 BKC kernels typically ship with CONFIG_I2C_ATR disabled, so # users must rebuild those kernels with CONFIG_I2C_ATR=y to use maxim-serdes. # Skip on 6.12-intel and 6.17-intel. 6.18-intel and newer are allowed. -KERNEL_SKIP_MAXIM_SERDES=0 -if [ "$KERNEL_VER" -eq 6 ] && { [ "$KERNEL_PATCH" -eq 12 ] || [ "$KERNEL_PATCH" -eq 17 ]; } && echo "$kernelver" | grep -q -- '-intel'; then +#KERNEL_SKIP_MAXIM_SERDES=0 +#if [ "$KERNEL_VER" -eq 6 ] && { [ "$KERNEL_PATCH" -eq 12 ] || [ "$KERNEL_PATCH" -eq 17 ]; } && echo "$kernelver" | grep -q -- '-intel'; then KERNEL_SKIP_MAXIM_SERDES=1 -fi +#fi if [ "$KERNEL_SKIP_MAXIM_SERDES" -ne 1 ]; then BUILT_MODULE_NAME[$((++i))]="max-serdes" BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/maxim-serdes" diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile new file mode 100644 index 00000000..badba1ce --- /dev/null +++ b/drivers/i2c/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-m += i2c-atr.o diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c new file mode 100644 index 00000000..3ee05d7c --- /dev/null +++ b/drivers/i2c/i2c-atr.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C Address Translator + * + * Copyright (c) 2019,2022 Luca Ceresoli + * Copyright (c) 2022,2023 Tomi Valkeinen + * + * Originally based on i2c-mux.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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. + * @node: List node + * @client: Pointer to 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. + */ +struct i2c_atr_alias_pair { + struct list_head node; + const struct i2c_client *client; + u16 alias; +}; + +/** + * 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_list: List of @struct i2c_atr_alias_pair containing the + * assigned aliases + * @orig_addrs_lock: Mutex protecting @orig_addrs + * @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; + + struct list_head alias_list; + + /* Lock orig_addrs during xfer */ + struct mutex orig_addrs_lock; + 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) + * @max_adapters: Maximum number of adapters this I2C ATR can have + * @num_aliases: Number of aliases in the aliases array + * @aliases: The aliases array + * @alias_mask_lock: Lock protecting alias_use_mask + * @alias_use_mask: Bitmask for used aliases in aliases array + * @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; + int max_adapters; + + size_t num_aliases; + const u16 *aliases; + /* Protects alias_use_mask */ + spinlock_t alias_mask_lock; + unsigned long *alias_use_mask; + + struct notifier_block i2c_nb; + + struct i2c_adapter *adapter[] __counted_by(max_adapters); +}; + +static struct i2c_atr_alias_pair * +i2c_atr_find_mapping_by_client(const struct list_head *list, + const struct i2c_client *client) +{ + struct i2c_atr_alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client == client) + return c2a; + } + + return NULL; +} + +static struct i2c_atr_alias_pair * +i2c_atr_find_mapping_by_addr(const struct list_head *list, u16 phys_addr) +{ + struct i2c_atr_alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client->addr == phys_addr) + return c2a; + } + + return NULL; +} + +/* + * 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; + + /* 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; + } + + for (i = 0; i < num; i++) { + chan->orig_addrs[i] = msgs[i].addr; + + c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, + msgs[i].addr); + if (!c2a) + continue; + + msgs[i].addr = c2a->alias; + } + + return 0; +} + +/* + * 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) +{ + int i; + + for (i = 0; i < num; i++) + msgs[i].addr = chan->orig_addrs[i]; +} + +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; + + if (atr->ops->select) { + ret = atr->ops->select(atr, chan->chan_id); + if (ret < 0) + goto err_unmap; + } + + /* Perform the transfer */ + ret = i2c_transfer(parent, msgs, num); + + if (atr->ops->deselect) + atr->ops->deselect(atr, chan->chan_id); + +err_unmap: + /* 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; + + c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, addr); + if (!c2a) + return i2c_smbus_xfer(parent, addr, flags, read_write, command, + size, data); + + return i2c_smbus_xfer(parent, c2a->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_reserve_alias(struct i2c_atr *atr) +{ + unsigned long idx; + + spin_lock(&atr->alias_mask_lock); + + idx = find_first_zero_bit(atr->alias_use_mask, atr->num_aliases); + if (idx >= atr->num_aliases) { + spin_unlock(&atr->alias_mask_lock); + dev_err(atr->dev, "failed to find a free alias\n"); + return -EBUSY; + } + + set_bit(idx, atr->alias_use_mask); + + spin_unlock(&atr->alias_mask_lock); + + return atr->aliases[idx]; +} + +static void i2c_atr_release_alias(struct i2c_atr *atr, u16 alias) +{ + unsigned int idx; + + spin_lock(&atr->alias_mask_lock); + + for (idx = 0; idx < atr->num_aliases; ++idx) { + if (atr->aliases[idx] == alias) { + clear_bit(idx, atr->alias_use_mask); + spin_unlock(&atr->alias_mask_lock); + return; + } + } + + spin_unlock(&atr->alias_mask_lock); + + /* This should never happen */ + dev_warn(atr->dev, "Unable to find mapped alias\n"); +} + +static int i2c_atr_attach_client(struct i2c_adapter *adapter, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_atr_alias_pair *c2a; + u16 alias; + int ret; + + if (atr->num_aliases) { + ret = i2c_atr_reserve_alias(atr); + if (ret < 0) + return ret; + + alias = ret; + } else { + alias = client->addr; + } + + c2a = kzalloc(sizeof(*c2a), GFP_KERNEL); + if (!c2a) { + ret = -ENOMEM; + goto err_release_alias; + } + + ret = atr->ops->attach_client(atr, chan->chan_id, client, alias); + if (ret) + goto err_free; + + dev_dbg(atr->dev, "chan%u: client 0x%02x mapped at alias 0x%02x (%s)\n", + chan->chan_id, client->addr, alias, client->name); + + c2a->client = client; + c2a->alias = alias; + list_add(&c2a->node, &chan->alias_list); + + return 0; + +err_free: + kfree(c2a); +err_release_alias: + if (atr->num_aliases) + i2c_atr_release_alias(atr, alias); + + return ret; +} + +static void i2c_atr_detach_client(struct i2c_adapter *adapter, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_atr_alias_pair *c2a; + + atr->ops->detach_client(atr, chan->chan_id, client); + + c2a = i2c_atr_find_mapping_by_client(&chan->alias_list, client); + if (!c2a) { + /* This should never happen */ + dev_warn(atr->dev, "Unable to find address mapping\n"); + return; + } + + if (atr->num_aliases) + i2c_atr_release_alias(atr, c2a->alias); + + dev_dbg(atr->dev, + "chan%u: client 0x%02x unmapped from alias 0x%02x (%s)\n", + chan->chan_id, client->addr, c2a->alias, client->name); + + list_del(&c2a->node); + kfree(c2a); +} + +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_client(client->adapter, client); + 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_client(client->adapter, client); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + +static int i2c_atr_parse_alias_pool(struct i2c_atr *atr) +{ + struct device *dev = atr->dev; + unsigned long *alias_use_mask; + size_t num_aliases; + unsigned int i; + u32 *aliases32; + u16 *aliases16; + int ret; + + ret = fwnode_property_count_u32(dev_fwnode(dev), "i2c-alias-pool"); + if (ret < 0) { + dev_dbg(dev, "No i2c-alias-pool property; using identity aliases with passthrough\n"); + return 0; + } + + num_aliases = ret; + + if (!num_aliases) + return 0; + + aliases32 = kcalloc(num_aliases, sizeof(*aliases32), GFP_KERNEL); + if (!aliases32) + return -ENOMEM; + + 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; + } + + aliases16 = kcalloc(num_aliases, sizeof(*aliases16), GFP_KERNEL); + if (!aliases16) { + ret = -ENOMEM; + goto err_free_aliases32; + } + + for (i = 0; i < num_aliases; i++) { + if (!(aliases32[i] & 0xffff0000)) { + aliases16[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_aliases16; + } + + alias_use_mask = bitmap_zalloc(num_aliases, GFP_KERNEL); + if (!alias_use_mask) { + ret = -ENOMEM; + goto err_free_aliases16; + } + + kfree(aliases32); + + atr->num_aliases = num_aliases; + atr->aliases = aliases16; + atr->alias_use_mask = alias_use_mask; + + dev_dbg(dev, "i2c-alias-pool has %zu aliases", atr->num_aliases); + + return 0; + +err_free_aliases16: + kfree(aliases16); +err_free_aliases32: + kfree(aliases32); + return ret; +} + +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters) +{ + struct i2c_atr *atr; + int ret; + + if (max_adapters > ATR_MAX_ADAPTERS) + return ERR_PTR(-EINVAL); + + if (!ops || !ops->attach_client || !ops->detach_client) + return ERR_PTR(-EINVAL); + + atr = kzalloc(struct_size(atr, adapter, max_adapters), GFP_KERNEL); + if (!atr) + return ERR_PTR(-ENOMEM); + + mutex_init(&atr->lock); + spin_lock_init(&atr->alias_mask_lock); + + atr->parent = parent; + atr->dev = dev; + atr->ops = ops; + atr->max_adapters = max_adapters; + + if (parent->algo->master_xfer) + atr->algo.master_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_aliases; + + return atr; + +err_free_aliases: + bitmap_free(atr->alias_use_mask); + kfree(atr->aliases); +err_destroy_mutex: + mutex_destroy(&atr->lock); + 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); + bitmap_free(atr->alias_use_mask); + kfree(atr->aliases); + mutex_destroy(&atr->lock); + kfree(atr); +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, I2C_ATR); + +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct device *adapter_parent, + struct fwnode_handle *bus_handle) +{ + struct i2c_adapter *parent = atr->parent; + struct device *dev = atr->dev; + struct i2c_atr_chan *chan; + char symlink_name[ATR_MAX_SYMLINK_LEN]; + int ret; + + 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 (!adapter_parent) + adapter_parent = dev; + + chan->atr = atr; + chan->chan_id = chan_id; + INIT_LIST_HEAD(&chan->alias_list); + mutex_init(&chan->orig_addrs_lock); + + 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 = adapter_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); + } + + 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_fwnode_put; + } + + 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_fwnode_put: + fwnode_handle_put(dev_fwnode(&chan->adap.dev)); + mutex_destroy(&chan->orig_addrs_lock); + 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); + + atr->adapter[chan_id] = NULL; + + fwnode_handle_put(fwnode); + mutex_destroy(&chan->orig_addrs_lock); + 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); + +struct i2c_adapter *i2c_atr_get_adapter(struct i2c_atr *atr, u32 chan_id) +{ + if (!atr || chan_id >= atr->max_adapters) + return NULL; + + return atr->adapter[chan_id]; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_adapter, I2C_ATR); + +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_DESCRIPTION("I2C Address Translator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/isx031.c b/drivers/media/i2c/isx031.c index e8c49016..1c7dd965 100644 --- a/drivers/media/i2c/isx031.c +++ b/drivers/media/i2c/isx031.c @@ -140,6 +140,21 @@ static const struct isx031_reg isx031_framesync_reg[] = { {} }; +static const struct isx031_reg isx031_framesync_stream_reg[] = { + {ISX031_REG_LEN_08BIT, 0x8AF0, 0x02}, /*1:External pulse-based sync, 2:Shutter trigger-based */ + {ISX031_REG_LEN_08BIT, 0x8AF1, 0x00}, /*0:Level detection auto mode disabled, 1: auto mode enable */ + {ISX031_REG_LEN_08BIT, 0x8AFE, 0x00}, + {ISX031_REG_LEN_08BIT, 0x8AFF, 0x0C}, + {ISX031_REG_LEN_08BIT, 0xBF14, 0x02}, /* SG_MODE_APL */ + {} +}; + +static const struct isx031_reg_list isx031_framesync_stream_reg_list = { + .num_of_regs = ARRAY_SIZE(isx031_framesync_stream_reg), + .regs = isx031_framesync_stream_reg, +}; + + static const struct isx031_reg isx031_1920_1536_30fps_reg[] = { {ISX031_REG_LEN_08BIT, 0x8AA8, 0x01}, /* Crop enable */ {ISX031_REG_LEN_08BIT, 0x8AAA, 0x80}, /* H size = 1920 */ @@ -514,6 +529,12 @@ static int isx031_initialize_module(struct isx031 *isx031) } } + ret = isx031_write_reg_list(client, &isx031_framesync_stream_reg_list, true); + if (ret) { + dev_err(&client->dev, "Failed to set framesync reg\n"); + return ret; + } + return 0; } @@ -623,6 +644,7 @@ static int isx031_start_streaming(struct isx031 *isx031) return ret; } + return 0; } @@ -1022,19 +1044,12 @@ static int isx031_probe(struct i2c_client *client) isx031->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(isx031->reset_gpio)) - return -EPROBE_DEFER; + return dev_err_probe(&client->dev, PTR_ERR(isx031->reset_gpio), + "Failed to get reset gpio\n"); if (isx031->reset_gpio) dev_info(&client->dev, "Reset gpio found\n"); else dev_warn(&client->dev, "Reset gpio not found\n"); - - isx031->fsin_gpio = devm_gpiod_get_optional(&client->dev, "fsin", - GPIOD_OUT_LOW); - if (isx031->fsin_gpio) - dev_info(&client->dev, "Fsin gpio found\n"); - else - dev_warn(&client->dev, "Fsin gpio not found\n"); - /* Initialize subdevice */ sd = &isx031->sd; v4l2_i2c_subdev_init(sd, client, &isx031_subdev_ops); diff --git a/drivers/media/i2c/max9x/max9295.c b/drivers/media/i2c/max9x/max9295.c index b2757356..afa70a56 100644 --- a/drivers/media/i2c/max9x/max9295.c +++ b/drivers/media/i2c/max9x/max9295.c @@ -638,6 +638,29 @@ static int max9295_verify_devid(struct max9x_common *common) return 0; } +#define DESCH_SER_FSYNC_GPIO 7 +#define DESCH_SER_FSYNC_RX_ID 7 + +static int max9295_configure_frame_sync(struct regmap *map) +{ + int ret; + + ret = regmap_update_bits(map, MAX9295_GPIO_C(DESCH_SER_FSYNC_GPIO), + MAX9295_GPIO_C_RX_ID, + FIELD_PREP(MAX9295_GPIO_C_RX_ID, + DESCH_SER_FSYNC_RX_ID)); + if (ret) + return ret; + + return regmap_update_bits(map, MAX9295_GPIO_A(DESCH_SER_FSYNC_GPIO), + MAX9295_GPIO_A_RES_CFG_FIELD | + MAX9295_GPIO_A_TX_EN_FIELD | + MAX9295_GPIO_A_RX_EN_FIELD | + MAX9295_GPIO_A_OUT_DIS_FIELD, + MAX9295_GPIO_A_RES_CFG_FIELD | + MAX9295_GPIO_A_RX_EN_FIELD); +} + static int max9295_enable(struct max9x_common *common) { struct device *dev = common->dev; @@ -660,6 +683,8 @@ static int max9295_enable(struct max9x_common *common) /* Clear the csi port selections */ TRY(ret, regmap_write_retry(map, MAX9295_FRONTTOP_0, MAX9295_FRONTTOP_0_LINE_INFO)); + TRY(ret, max9295_configure_frame_sync(map)); + return 0; } diff --git a/drivers/media/i2c/max9x/max9296.c b/drivers/media/i2c/max9x/max9296.c index b7ba93b5..f5d5b0b1 100644 --- a/drivers/media/i2c/max9x/max9296.c +++ b/drivers/media/i2c/max9x/max9296.c @@ -30,6 +30,11 @@ #include "max9296.h" +#define MAX9296_GMSL2_3GBPS_MHZ 3000 +#define MAX9296_MAX96717_SRC_PIPE_Z 2 +#define MAX9296_MIPI_CSI2_DT_FS 0x00 +#define MAX9296_MIPI_CSI2_DT_FE 0x01 + // Params int max9296_serial_link_timeout_ms = MAX9296_DEFAULT_SERIAL_LINK_TIMEOUT_MS; module_param(max9296_serial_link_timeout_ms, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); @@ -58,11 +63,16 @@ static int max9296_set_video_pipe_map(struct max9x_common *common, unsigned int static int max9296_set_csi_double_loading_mode(struct max9x_common *common, unsigned int csi_id, unsigned int bpp); static int max9296_set_csi_link_enabled(struct max9x_common *common, unsigned int csi_id, bool enable); static int max9296_set_video_pipe_enabled(struct max9x_common *common, unsigned int pipe_id, bool enable); +static int max9296_set_csi_video_pipes_enabled(struct max9x_common *common, + unsigned int csi_id, + bool enable); static int max9296_set_serial_link_routing(struct max9x_common *common, unsigned int link_id); static int max9296_disable_serial_link(struct max9x_common *common, unsigned int link_id); static int max9296_enable_serial_link(struct max9x_common *common, unsigned int link_id); static int max9296_isolate_serial_link(struct max9x_common *common, unsigned int link); static int max9296_deisolate_serial_link(struct max9x_common *common, unsigned int link); +static int max9296_select_serial_link(struct max9x_common *common, unsigned int link); +static int max9296_deselect_serial_link(struct max9x_common *common, unsigned int link); static int max9296_wait_link_lock(struct max9x_common *common, int link); static int max9296_enable_csi_link(struct max9x_common *common, unsigned int csi_link_id); static int max9296_disable_csi_link(struct max9x_common *common, unsigned int csi_link_id); @@ -154,6 +164,7 @@ static int max9296_set_initial_deskew(struct max9x_common *common, unsigned int { struct device *dev = common->dev; struct regmap *map = common->map; + int ret; dev_dbg(dev, "CSI link %d: Initial deskew %s", csi_id, enable ? "enabled" : "disabled"); @@ -161,6 +172,14 @@ static int max9296_set_initial_deskew(struct max9x_common *common, unsigned int if (width > 7) width = 7; + /* Keep one-shot initial deskew, but avoid periodic deskew while streaming. + * On the dual-MAX9295 path the periodic calibration coincides with CSI-2 + * packet header/CRC errors and pink frames. + */ + ret = regmap_write(map, MAX9296_MIPI_TX_DESKEW_PERIODIC(csi_id), 0); + if (ret) + return ret; + return regmap_write(map, MAX9296_MIPI_TX_DESKEW_INIT(csi_id), MAX9X_FIELD_PREP(MAX9296_MIPI_TX_DESKEW_INIT_AUTO_EN, enable) | MAX9X_FIELD_PREP(MAX9296_MIPI_TX_DESKEW_INIT_WIDTH, width)); @@ -413,25 +432,27 @@ static int max9296_set_video_pipe_maps_enabled(struct max9x_common *common, unsi return 0; } -static int max9296_set_video_pipe_map(struct max9x_common *common, unsigned int pipe_id, - unsigned int map_id, struct max9x_serdes_mipi_map *mipi_map) +static int max9296_set_video_pipe_map_dt(struct max9x_common *common, + unsigned int pipe_id, unsigned int map_id, + struct max9x_serdes_mipi_map *mipi_map, + unsigned int src_dt, unsigned int dst_dt) { struct device *dev = common->dev; struct regmap *map = common->map; int ret; dev_dbg(dev, "Video-pipe %d, map %d: VC%d:DT%02x->VC%d:DT%02x, dst_csi=%d ", - pipe_id, map_id, mipi_map->src_vc, mipi_map->src_dt, - mipi_map->dst_vc, mipi_map->dst_dt, mipi_map->dst_csi); + pipe_id, map_id, mipi_map->src_vc, src_dt, + mipi_map->dst_vc, dst_dt, mipi_map->dst_csi); TRY(ret, regmap_write(map, MAX9296_MAP_SRC_L(pipe_id, map_id), MAX9X_FIELD_PREP(MAX9296_MAP_SRC_L_VC_FIELD, mipi_map->src_vc) | - MAX9X_FIELD_PREP(MAX9296_MAP_SRC_L_DT_FIELD, mipi_map->src_dt)) + MAX9X_FIELD_PREP(MAX9296_MAP_SRC_L_DT_FIELD, src_dt)) ); TRY(ret, regmap_write(map, MAX9296_MAP_DST_L(pipe_id, map_id), MAX9X_FIELD_PREP(MAX9296_MAP_DST_L_VC_FIELD, mipi_map->dst_vc) | - MAX9X_FIELD_PREP(MAX9296_MAP_DST_L_DT_FIELD, mipi_map->dst_dt)) + MAX9X_FIELD_PREP(MAX9296_MAP_DST_L_DT_FIELD, dst_dt)) ); TRY(ret, regmap_write(map, MAX9296_MAP_SRCDST_H(pipe_id, map_id), @@ -447,6 +468,13 @@ static int max9296_set_video_pipe_map(struct max9x_common *common, unsigned int return 0; } +static int max9296_set_video_pipe_map(struct max9x_common *common, unsigned int pipe_id, + unsigned int map_id, struct max9x_serdes_mipi_map *mipi_map) +{ + return max9296_set_video_pipe_map_dt(common, pipe_id, map_id, mipi_map, + mipi_map->src_dt, mipi_map->dst_dt); +} + /** * max9296_set_csi_double_loading_mode() - Configure Double Loading Mode on a CSI controller * @common: max9x_common @@ -519,23 +547,32 @@ static int max9296_set_csi_link_enabled(struct max9x_common *common, unsigned in if (enable && csi_link->usecount == 0) { // Enable && first user + ret = max9296_set_csi_video_pipes_enabled(common, csi_id, false); + if (ret) + goto err_unlock; + ret = max9296_set_initial_deskew(common, csi_id, csi_link->config.auto_init_deskew_enabled, csi_link->config.initial_deskew_width); if (ret) - goto err_unlock; + goto err_enable_pipes; ret = max9296_set_phy_dpll_freq(common, csi_id, csi_link->config.freq_mhz); if (ret) - goto err_unlock; + goto err_enable_pipes; ret = max9296_set_phy_dpll_enabled(common, csi_id, true); if (ret) - goto err_unlock; + goto err_enable_pipes; ret = max9296_set_phy_enabled(common, csi_id, true); if (ret) - goto err_unlock; + goto err_enable_pipes; + usleep_range(10000, 11000); + + ret = max9296_set_csi_video_pipes_enabled(common, csi_id, true); + if (ret) + goto err_unlock; } else if (!enable && csi_link->usecount == 1) { // Disable && no more users ret = max9296_set_phy_enabled(common, csi_id, false); @@ -556,6 +593,10 @@ static int max9296_set_csi_link_enabled(struct max9x_common *common, unsigned in mutex_unlock(&csi_link->csi_mutex); return ret; + +err_enable_pipes: + max9296_set_csi_video_pipes_enabled(common, csi_id, true); + goto err_unlock; } static int max9296_set_video_pipe_enabled(struct max9x_common *common, unsigned int pipe_id, bool enable) @@ -570,6 +611,40 @@ static int max9296_set_video_pipe_enabled(struct max9x_common *common, unsigned MAX9X_FIELD_PREP(MAX9296_VIDEO_PIPE_EN_FIELD(pipe_id), enable ? 1U : 0U)); } +static int max9296_set_csi_video_pipes_enabled(struct max9x_common *common, + unsigned int csi_id, + bool enable) +{ + unsigned int pipe_id; + int ret; + + for (pipe_id = 0; pipe_id < common->num_video_pipes; pipe_id++) { + struct max9x_serdes_pipe_config *config; + unsigned int map_id; + bool pipe_uses_csi = false; + + if (!common->video_pipe[pipe_id].enabled) + continue; + + config = &common->video_pipe[pipe_id].config; + for (map_id = 0; map_id < config->num_maps; map_id++) { + if (config->map[map_id].dst_csi == csi_id) { + pipe_uses_csi = true; + break; + } + } + + if (!pipe_uses_csi) + continue; + + ret = max9296_set_video_pipe_enabled(common, pipe_id, enable); + if (ret) + return ret; + } + + return 0; +} + /***** max9296_serial_link_ops auxiliary functions *****/ static int max9296_set_serial_link_rate(struct max9x_common *common, unsigned int link_id) { @@ -577,6 +652,7 @@ static int max9296_set_serial_link_rate(struct max9x_common *common, unsigned in struct regmap *map = common->map; struct max9x_serdes_serial_config *config = &common->serial_link[link_id].config; int tx_rate, rx_rate; + int ret; tx_rate = max9x_serdes_mhz_to_rate(max9296_tx_rates, ARRAY_SIZE(max9296_tx_rates), config->tx_freq_mhz); if (tx_rate < 0) @@ -588,12 +664,36 @@ static int max9296_set_serial_link_rate(struct max9x_common *common, unsigned in dev_dbg(dev, "Serial-link %d: TX=%d MHz RX=%d MHz", link_id, config->tx_freq_mhz, config->rx_freq_mhz); + ret = regmap_update_bits(map, MAX9296_PHY_REM_CTRL, + MAX9296_PHY_REM_CTRL_TX_FIELD, + MAX9X_FIELD_PREP(MAX9296_PHY_REM_CTRL_TX_FIELD, tx_rate)); + if (ret) + return ret; + return regmap_update_bits(map, MAX9296_PHY_REM_CTRL, - MAX9296_PHY_REM_CTRL_TX_FIELD | MAX9296_PHY_REM_CTRL_RX_FIELD, - MAX9X_FIELD_PREP(MAX9296_PHY_REM_CTRL_TX_FIELD, tx_rate) | + MAX9296_PHY_REM_CTRL_RX_FIELD, MAX9X_FIELD_PREP(MAX9296_PHY_REM_CTRL_RX_FIELD, rx_rate)); } +static void max9296_force_group_3gbps_pipe_z(struct max9x_common *common) +{ + unsigned int i; + + /* MAX9296 uses one GMSL2 forward rate for the whole deserializer. + * If a generic module does not lock at 6Gbps, retry the whole DES group + * at 3Gbps so the serializer can be probed and identified by DEV_ID. + */ + for (i = 0; i < common->num_serial_links; i++) { + if (common->serial_link[i].enabled) + common->serial_link[i].config.rx_freq_mhz = MAX9296_GMSL2_3GBPS_MHZ; + } + + /* + * Leave video_pipe[].config.src_pipe unchanged. For MAX9296 this field + * selects GMSL stream-id, and dual MAX96717 links require unique ids. + */ +} + static int max9296_set_serial_link_routing(struct max9x_common *common, unsigned int link_id) { unsigned int pipe_id; @@ -614,20 +714,49 @@ static int max9296_set_serial_link_routing(struct max9x_common *common, unsigned if (ret) return ret; - ret = max9296_set_video_pipe_maps_enabled(common, pipe_id, config->num_maps); + bool has_fs_map = false; + bool has_fe_map = false; + unsigned int remap_id = 0; + + for (map_id = 0; map_id < config->num_maps; map_id++) { + if (config->map[map_id].src_dt == MAX9296_MIPI_CSI2_DT_FS) + has_fs_map = true; + if (config->map[map_id].src_dt == MAX9296_MIPI_CSI2_DT_FE) + has_fe_map = true; + } + + ret = max9296_set_video_pipe_maps_enabled(common, pipe_id, + (has_fs_map && has_fe_map) ? config->num_maps : config->num_maps * 3); if (ret) return ret; for (map_id = 0; map_id < config->num_maps; map_id++) { - ret = max9296_set_video_pipe_map(common, pipe_id, map_id, &config->map[map_id]); + ret = max9296_set_video_pipe_map(common, pipe_id, remap_id++, &config->map[map_id]); if (ret) return ret; + if (has_fs_map && has_fe_map) + goto set_double_loading; + + ret = max9296_set_video_pipe_map_dt(common, pipe_id, remap_id++, + &config->map[map_id], + MAX9296_MIPI_CSI2_DT_FS, + MAX9296_MIPI_CSI2_DT_FS); + if (ret) + return ret; + + ret = max9296_set_video_pipe_map_dt(common, pipe_id, remap_id++, + &config->map[map_id], + MAX9296_MIPI_CSI2_DT_FE, + MAX9296_MIPI_CSI2_DT_FE); + if (ret) + return ret; + +set_double_loading: ret = max9296_set_csi_double_loading_mode(common, config->map[map_id].dst_csi, config->dbl_pixel_bpp); if (ret) return ret; - if (!config->map[map_id].is_csi_enabled && common->csi_link[config->map[map_id].dst_csi].config.auto_start) { ret = max9296_set_csi_link_enabled(common, config->map[map_id].dst_csi, true); if (ret) @@ -691,26 +820,42 @@ static int max9296_wait_link_lock(struct max9x_common *common, int link) /***** max9296_serial_link_ops auxiliary functions *****/ /***** max9296_serial_link_ops *****/ -static int max9296_isolate_serial_link(struct max9x_common *common, unsigned int link) +static int max9296_set_selected_links(struct max9x_common *common, + unsigned int link_cfg, bool reset) { struct device *dev = common->dev; struct regmap *map = common->map; + unsigned int fields; + unsigned int vals; + int ret; + + TRY_DEV_HERE(ret, regmap_update_bits(map, MAX9296_GMSL1_EN, + MAX9296_GMSL1_EN_LINK_EN_FIELD, + FIELD_PREP(MAX9296_GMSL1_EN_LINK_EN_FIELD, link_cfg)), + dev); + + fields = MAX9296_CTRL0_AUTO_CFG_FIELD | MAX9296_CTRL0_LINK_CFG_FIELD; + vals = FIELD_PREP(MAX9296_CTRL0_AUTO_CFG_FIELD, 0) | + FIELD_PREP(MAX9296_CTRL0_LINK_CFG_FIELD, link_cfg); + if (reset) { + fields |= MAX9296_CTRL0_RESET_ONESHOT_FIELD; + vals |= FIELD_PREP(MAX9296_CTRL0_RESET_ONESHOT_FIELD, 1); + } + + return regmap_update_bits(map, MAX9296_CTRL0, fields, vals); +} + +static int max9296_isolate_serial_link(struct max9x_common *common, unsigned int link) +{ + struct device *dev = common->dev; unsigned int link_cfg; - unsigned int auto_link; int ret; dev_dbg(dev, "Isolate link %d", link); - auto_link = 0; link_cfg = (link == 0) ? MAX9296_LINK_A : MAX9296_LINK_B; - TRY_DEV_HERE(ret, regmap_update_bits(map, MAX9296_CTRL0, - MAX9296_CTRL0_AUTO_CFG_FIELD | MAX9296_CTRL0_LINK_CFG_FIELD, - FIELD_PREP(MAX9296_CTRL0_AUTO_CFG_FIELD, auto_link) - | FIELD_PREP(MAX9296_CTRL0_LINK_CFG_FIELD, link_cfg)), - dev); - - TRY_DEV_HERE(ret, max9296_serial_link_reset(common, link), dev); + TRY_DEV_HERE(ret, max9296_set_selected_links(common, link_cfg, true), dev); TRY_DEV_HERE(ret, max9296_wait_link_lock(common, link), dev); @@ -720,9 +865,7 @@ static int max9296_isolate_serial_link(struct max9x_common *common, unsigned int static int max9296_deisolate_serial_link(struct max9x_common *common, unsigned int link) { struct device *dev = common->dev; - struct regmap *map = common->map; unsigned int link_cfg; - unsigned int auto_link = 0; int ret; bool link_a = common->serial_link[0].detected; bool link_b = common->serial_link[1].detected; @@ -740,22 +883,23 @@ static int max9296_deisolate_serial_link(struct max9x_common *common, unsigned i dev_dbg(dev, "Deisolate link %d (link_cfg=%d)", link, link_cfg); - TRY_DEV_HERE(ret, regmap_update_bits( - map, - MAX9296_CTRL0, - MAX9296_CTRL0_AUTO_CFG_FIELD - |MAX9296_CTRL0_LINK_CFG_FIELD, - FIELD_PREP(MAX9296_CTRL0_AUTO_CFG_FIELD, auto_link) - |FIELD_PREP(MAX9296_CTRL0_LINK_CFG_FIELD, link_cfg)), - dev); - - TRY_DEV_HERE(ret, max9296_serial_link_reset(common, link), dev); + TRY_DEV_HERE(ret, max9296_set_selected_links(common, link_cfg, true), dev); TRY_DEV_HERE(ret, max9296_wait_link_lock(common, link), dev); return 0; } +static int max9296_select_serial_link(struct max9x_common *common, unsigned int link) +{ + return max9296_isolate_serial_link(common, link); +} + +static int max9296_deselect_serial_link(struct max9x_common *common, unsigned int link) +{ + return max9296_deisolate_serial_link(common, link); +} + static int max9296_enable_serial_link(struct max9x_common *common, unsigned int link_id) { int ret; @@ -773,6 +917,19 @@ static int max9296_enable_serial_link(struct max9x_common *common, unsigned int return ret; ret = max9296_isolate_serial_link(common, link_id); + if (ret == -ETIMEDOUT && + common->serial_link[link_id].config.rx_freq_mhz != MAX9296_GMSL2_3GBPS_MHZ) { + dev_info(common->dev, + "Serial-link %u did not lock at 6Gbps; retrying DES group at 3Gbps before serializer DEV_ID probe", + link_id); + max9296_force_group_3gbps_pipe_z(common); + + ret = max9296_set_serial_link_rate(common, link_id); + if (ret) + return ret; + + ret = max9296_isolate_serial_link(common, link_id); + } if (ret) return ret; @@ -827,6 +984,8 @@ static int max9296_disable_serial_link(struct max9x_common *common, unsigned int static struct max9x_serial_link_ops max9296_serial_link_ops = { .enable = max9296_enable_serial_link, .disable = max9296_disable_serial_link, + .select = max9296_select_serial_link, + .deselect = max9296_deselect_serial_link, .isolate = max9296_isolate_serial_link, .deisolate = max9296_deisolate_serial_link, }; @@ -847,9 +1006,82 @@ static struct max9x_csi_link_ops max9296_csi_link_ops = { .disable = max9296_disable_csi_link, }; + +#define MAX9296A_FSYNC_0 0x3e0 +#define MAX9296A_FSYNC_0_OUT_PIN BIT(5) +#define MAX9296A_FSYNC_0_EN_VS_GEN BIT(4) +#define MAX9296A_FSYNC_0_MODE GENMASK(3, 2) +#define MAX9296A_FSYNC_0_METHOD GENMASK(1, 0) + + +#define MAX9296A_REG3 0x3 +#define MAX9296A_REG3_UART_1_EN BIT(6) + + + +#define MAX9296A_GPIO_A(x) (0x2b0 + (x) * 0x3) +#define MAX9296A_GPIO_A_RES_CFG BIT(7) +#define MAX9296A_GPIO_A_TX_EN BIT(1) +#define MAX9296A_GPIO_A_OUT_DIS BIT(0) +#define MAX9296A_GPIO_B(x) (0x2b1 + (x) * 0x3) +#define MAX9296A_GPIO_B_TX_ID GENMASK(4, 0) +#define MAX9296A_GPIO_C(x) (0x2b2 + (x) * 0x3) + + +#define DES_FSYNC_MODE 2 +#define DES_FSYNC_METHOD 0 +#define DES_FSYNC_INPUT_PIN 6 +#define DES_FSYNC_TX_ID 7 + + +static int max9296_configure_frame_sync(struct regmap *map) +{ + unsigned int fsync0; + unsigned int val = BIT(5) | (DES_FSYNC_TX_ID & GENMASK(4, 0)); + int ret; + + fsync0 = FIELD_PREP(MAX9296A_FSYNC_0_MODE, DES_FSYNC_MODE) | + FIELD_PREP(MAX9296A_FSYNC_0_METHOD, DES_FSYNC_METHOD); + + + ret = regmap_write(map, MAX9296A_FSYNC_0, fsync0); + if (ret) + return ret; + + + if (DES_FSYNC_INPUT_PIN == 6) { + /* + * MFP6 shares the UART1/TX1 function on MAX9296A. Clear + * UART_1_EN so the pad is available to the GPIO forwarding + * path before arming it as an external FSYNC input. + */ + ret = regmap_update_bits(map, MAX9296A_REG3, + MAX9296A_REG3_UART_1_EN, 0); + if (ret) + return ret; + } + + //MAX9296A_FSYNC_MODE_SLAVE + ret = regmap_write(map, MAX9296A_GPIO_A(DES_FSYNC_INPUT_PIN), + MAX9296A_GPIO_A_RES_CFG | + MAX9296A_GPIO_A_TX_EN | + MAX9296A_GPIO_A_OUT_DIS); + if (ret) + return ret; + + ret = regmap_write(map, MAX9296A_GPIO_B(DES_FSYNC_INPUT_PIN), val); + if (ret) + return ret; + + return regmap_write(map, MAX9296A_GPIO_C(DES_FSYNC_INPUT_PIN), val); + + +} + static int max9296_enable(struct max9x_common *common) { struct device *dev = common->dev; + struct regmap *map = common->map; int link_id; int ret; @@ -863,6 +1095,10 @@ static int max9296_enable(struct max9x_common *common) ret = max9296_configure_csi_dphy(common); + if (ret) + return ret; + + ret = max9296_configure_frame_sync(map); if (ret) return ret; diff --git a/drivers/media/i2c/max9x/max9296.h b/drivers/media/i2c/max9x/max9296.h index acc6bb03..88bf090d 100644 --- a/drivers/media/i2c/max9x/max9296.h +++ b/drivers/media/i2c/max9x/max9296.h @@ -67,6 +67,10 @@ enum max9296_link_mode { #define MAX9296_PHY_REM_CTRL_TX_FIELD (GENMASK(1, 0) << 2) #define MAX9296_PHY_REM_CTRL_RX_FIELD GENMASK(1, 0) #define MAX9296_PHY_REM_CTRL_DIS_FIELD BIT(4) +#define MAX9296_PHY_REM_CTRL_B (0x4) +#define MAX9296_PHY_REM_CTRL_B_RX_FIELD GENMASK(1, 0) +#define MAX9296_GMSL1_EN 0xF00 +#define MAX9296_GMSL1_EN_LINK_EN_FIELD GENMASK(1, 0) /* *CTRL3(0x13) LINK_MODE is set to link A. @@ -102,6 +106,10 @@ enum max9296_link_mode { #define MAX9296_MIPI_TX_DESKEW_INIT(csi) (MAX9296_MIPI_TX(csi) + 0x03) #define MAX9296_MIPI_TX_DESKEW_INIT_AUTO_EN BIT(7) #define MAX9296_MIPI_TX_DESKEW_INIT_WIDTH GENMASK(2, 0) +#define MAX9296_MIPI_TX_DESKEW_PERIODIC(csi) (MAX9296_MIPI_TX(csi) + 0x04) +#define MAX9296_MIPI_TX_DESKEW_PERIODIC_EN BIT(7) +#define MAX9296_MIPI_TX_DESKEW_PERIODIC_INTERVAL_FIELD GENMASK(5, 3) +#define MAX9296_MIPI_TX_DESKEW_PERIODIC_WIDTH_FIELD GENMASK(2, 0) #define MAX9296_MAP_EN_L(pipe) (MAX9296_MIPI_TX(pipe) + 0x0B) #define MAX9296_MAP_EN_H(pipe) (MAX9296_MIPI_TX(pipe) + 0x0C) #define MAX9296_MAP_EN_FIELD GENMASK(7, 0) diff --git a/drivers/media/i2c/max9x/max96717.c b/drivers/media/i2c/max9x/max96717.c index 12b5fb21..87b61d55 100644 --- a/drivers/media/i2c/max9x/max96717.c +++ b/drivers/media/i2c/max9x/max96717.c @@ -26,11 +26,13 @@ #include #include #include +#include #include #include #include #include "max96717.h" +#include "regmap-retry.h" static const struct regmap_config max96717_regmap_config = { .reg_bits = 16, @@ -41,12 +43,15 @@ static const struct regmap_config max96717_regmap_config = { static int max96717_set_pipe_csi_enabled(struct max9x_common *common, unsigned int pipe_id, unsigned int csi_id, bool enable); static int max96717_video_pipe_double_pixel(struct max9x_common *common, unsigned int pipe_id, unsigned int bpp); +static int max96717_video_pipe_mode(struct max9x_common *common, unsigned int pipe_id); +static int max96717_video_pipe_enabled(struct max9x_common *common, unsigned int pipe_id, bool enable); static int max96717_max_elements(struct max9x_common *common, enum max9x_element_type element); static int max96717_enable_serial_link(struct max9x_common *common, unsigned int link); static int max96717_disable_serial_link(struct max9x_common *common, unsigned int link); static int max96717_enable(struct max9x_common *common); static int max96717_disable(struct max9x_common *common); static int max96717_pixel_mode(struct max9x_common *common, bool pixel); +static int max96717_mandatory_init(struct max9x_common *common); static struct max9x_common *from_gpio_chip(struct gpio_chip *chip); static int max96717_gpio_get_direction(struct gpio_chip *chip, unsigned int offset); @@ -71,7 +76,6 @@ static struct max9x_serial_link_ops max96717_serial_link_ops = { .disable = max96717_disable_serial_link, }; -static struct max9x_translation_ops max96717_translation_ops; static struct max9x_common *from_gpio_chip(struct gpio_chip *chip) { @@ -179,8 +183,6 @@ static int max96717_setup_gpio(struct max9x_common *common) dev_dbg(dev, "gpio_chip label is %s, dev_name is %s", common->gpio_chip.label, dev_name(dev)); - // Functions - common->gpio_chip.label = MAX96717_NAME; common->gpio_chip.parent = dev; common->gpio_chip.get_direction = max96717_gpio_get_direction; common->gpio_chip.direction_input = max96717_gpio_direction_input; @@ -208,12 +210,29 @@ static int max96717_set_pipe_csi_enabled(struct max9x_common *common, { struct device *dev = common->dev; struct regmap *map = common->map; + unsigned int phy_cfg; int ret; + if (csi_id >= MAX96717_NUM_CSI_LINKS) + return -EINVAL; + dev_dbg(dev, "Video-pipe %d, csi %d: %s, %d lanes", \ pipe_id, csi_id, (enable ? "enable" : "disable"), \ common->csi_link[csi_id].config.num_lanes); + /* + * MAX96717 has a single CSI receiver, but it can be connected to PHY A + * or PHY B. The ACPI-generated pdata currently uses PHY B (csi_id 1). + * Program the PHY mode explicitly instead of relying on reset defaults. + */ + phy_cfg = csi_id == 0 ? MAX96717_MIPI_RX_0_PHY_CFG_A_ONLY : + MAX96717_MIPI_RX_0_PHY_CFG_B_ONLY; + ret = regmap_update_bits(map, MAX96717_MIPI_RX_0, + MAX96717_MIPI_RX_0_PHY_CFG_FIELD, + MAX9X_FIELD_PREP(MAX96717_MIPI_RX_0_PHY_CFG_FIELD, phy_cfg)); + if (ret) + return ret; + // Select number of lanes for CSI port csi_id ret = regmap_update_bits(map, MAX96717_MIPI_RX_1, MAX96717_MIPI_RX_1_SEL_CSI_LANES_FIELD(csi_id), @@ -241,6 +260,52 @@ static int max96717_set_pipe_csi_enabled(struct max9x_common *common, return 0; } +static int max96717_data_type_reg(unsigned int pipe_id, unsigned int data_type_slot, + unsigned int *reg) +{ + if (data_type_slot >= MAX96717_NUM_DATA_TYPES) + return -EINVAL; + + if (data_type_slot < 2) + *reg = MAX96717_FRONTTOP_12(pipe_id, data_type_slot); + else + *reg = MAX96717_EXTA(data_type_slot - 2); + + return 0; +} + +static int max96717_set_pipe_data_types_enabled(struct max9x_common *common, + unsigned int pipe_id, bool enable) +{ + struct device *dev = common->dev; + struct regmap *map = common->map; + unsigned int data_type_slot; + int ret; + + for (data_type_slot = 0; + data_type_slot < common->video_pipe[pipe_id].config.num_data_types; + data_type_slot++) { + unsigned int dt = common->video_pipe[pipe_id].config.data_type[data_type_slot]; + unsigned int reg; + + ret = max96717_data_type_reg(pipe_id, data_type_slot, ®); + if (ret) + return ret; + + dev_dbg(dev, "Video-pipe %d, data type %d: (%#.2x: %s)", + pipe_id, data_type_slot, dt, enable ? "enable" : "disable"); + + ret = regmap_update_bits_retry(map, reg, + MAX96717_MEM_DT_SEL_FIELD | MAX96717_MEM_DT_EN_FIELD, + MAX9X_FIELD_PREP(MAX96717_MEM_DT_SEL_FIELD, dt) | + MAX9X_FIELD_PREP(MAX96717_MEM_DT_EN_FIELD, enable ? 1U : 0U)); + if (ret) + return ret; + } + + return 0; +} + /** * max96717_video_pipe_double_pixel() - Configure Double Loading Mode on a video pipe * @common: max9x_common @@ -328,6 +393,70 @@ static int max96717_video_pipe_double_pixel(struct max9x_common *common, return regmap_update_bits(map, reg, fields, vals); } +static int max96717_video_pipe_mode(struct max9x_common *common, unsigned int pipe_id) +{ + struct max9x_serdes_pipe_config *config = &common->video_pipe[pipe_id].config; + struct regmap *map = common->map; + bool soft_bpp_enabled = false; + unsigned int max_bpp = 0; + unsigned int min_bpp = 0; + int ret; + + if (config->soft_min_pixel_bpp && config->soft_max_pixel_bpp) { + soft_bpp_enabled = true; + min_bpp = config->soft_min_pixel_bpp; + max_bpp = config->soft_max_pixel_bpp; + } else if (config->dbl_pixel_bpp) { + soft_bpp_enabled = true; + min_bpp = config->dbl_pixel_bpp * 2; + } + + ret = regmap_update_bits(map, MAX96717_VIDEO_TX0(pipe_id), + MAX96717_VIDEO_TX0_AUTO_BPP_EN_FIELD, + MAX9X_FIELD_PREP(MAX96717_VIDEO_TX0_AUTO_BPP_EN_FIELD, soft_bpp_enabled ? 0U : 1U)); + if (ret) + return ret; + + if (soft_bpp_enabled) { + ret = regmap_update_bits(map, MAX96717_VIDEO_TX1(pipe_id), + MAX96717_VIDEO_TX1_BPP_FIELD, + MAX9X_FIELD_PREP(MAX96717_VIDEO_TX1_BPP_FIELD, max_bpp)); + if (ret) + return ret; + } + + ret = regmap_update_bits(map, MAX96717_VIDEO_TX2(pipe_id), + MAX96717_VIDEO_TX2_DRIFT_DET_EN_FIELD, + MAX9X_FIELD_PREP(MAX96717_VIDEO_TX2_DRIFT_DET_EN_FIELD, soft_bpp_enabled ? 0U : 1U)); + if (ret) + return ret; + + return regmap_update_bits(map, MAX96717_FRONTTOP_2X(pipe_id), + MAX96717_FRONTTOP_2X_BPP_EN_FIELD | MAX96717_FRONTTOP_2X_BPP_FIELD, + MAX9X_FIELD_PREP(MAX96717_FRONTTOP_2X_BPP_EN_FIELD, soft_bpp_enabled ? 1U : 0U) | + MAX9X_FIELD_PREP(MAX96717_FRONTTOP_2X_BPP_FIELD, min_bpp)); +} + +static int max96717_video_pipe_enabled(struct max9x_common *common, unsigned int pipe_id, bool enable) +{ + return regmap_update_bits(common->map, MAX96717_REG2, + MAX96717_REG2_VID_TX_EN_FIELD(pipe_id), + MAX9X_FIELD_PREP(MAX96717_REG2_VID_TX_EN_FIELD(pipe_id), enable ? 1U : 0U)); +} + +static int max96717_video_pipe_stream_id(struct max9x_common *common, + unsigned int pipe_id, + unsigned int stream_id) +{ + struct device *dev = common->dev; + + dev_dbg(dev, "Video-pipe %d: stream_id=%u", pipe_id, stream_id); + + return regmap_update_bits(common->map, MAX96717_TX3(pipe_id), + MAX96717_TX3_TX_STR_SEL_FIELD, + MAX9X_FIELD_PREP(MAX96717_TX3_TX_STR_SEL_FIELD, stream_id)); +} + static int max96717_max_elements(struct max9x_common *common, enum max9x_element_type element) { @@ -340,6 +469,8 @@ static int max96717_max_elements(struct max9x_common *common, return MAX96717_NUM_MIPI_MAPS; case MAX9X_CSI_LINK: return MAX96717_NUM_CSI_LINKS; + case MAX9X_DATA_TYPES: + return MAX96717_NUM_DATA_TYPES; default: break; } @@ -362,6 +493,10 @@ static int max96717_enable_serial_link(struct max9x_common *common, continue; config = &common->video_pipe[pipe_id].config; + ret = max96717_set_pipe_data_types_enabled(common, pipe_id, + true); + if (ret) + return ret; ret = max96717_set_pipe_csi_enabled(common, pipe_id, config->src_csi, true); if (ret) @@ -370,6 +505,16 @@ static int max96717_enable_serial_link(struct max9x_common *common, config->dbl_pixel_bpp); if (ret) return ret; + ret = max96717_video_pipe_mode(common, pipe_id); + if (ret) + return ret; + ret = max96717_video_pipe_stream_id(common, pipe_id, + config->stream_id); + if (ret) + return ret; + ret = max96717_video_pipe_enabled(common, pipe_id, true); + if (ret) + return ret; } return 0; @@ -392,10 +537,19 @@ static int max96717_disable_serial_link(struct max9x_common *common, config = &common->video_pipe[pipe_id].config; + ret = max96717_set_pipe_data_types_enabled(common, pipe_id, + false); + if (ret) + return ret; + ret = max96717_set_pipe_csi_enabled(common, pipe_id, config->src_csi, false); if (ret) return ret; + + ret = max96717_video_pipe_enabled(common, pipe_id, false); + if (ret) + return ret; } return 0; @@ -422,21 +576,28 @@ static int max96717_pixel_mode(struct max9x_common *common, bool pixel) MAX9X_FIELD_PREP(MAX96717_TUNNEL_MODE, !pixel)); } +static int max96717_mandatory_init(struct max9x_common *common) +{ + return regmap_update_bits(common->map, MAX96717_CMU2, + MAX96717_CMU2_PFDDIV_RSHORT_FIELD, + MAX9X_FIELD_PREP(MAX96717_CMU2_PFDDIV_RSHORT_FIELD, + MAX96717_CMU2_PFDDIV_RSHORT_1_1V)); +} + /** * Enable the MAX96717 to replicate a frame sync signal from the deserializer. - * NOTE: Currently the MAX96717 driver supports frame sync across its - * GPIO8. + * NOTE: By default, the MAX96717 driver supports frame sync across GPIO7. */ static int max96717_enable_frame_sync(struct max9x_common *common) { - struct device_node *node = common->dev->of_node; struct device *dev = common->dev; struct regmap *map = common->map; int ret; - int deserializer_tx_id; - - ret = of_property_read_u32(node, "fsync-tx-id", &deserializer_tx_id); + u32 deserializer_tx_id=7; + u32 fsync_gpio = 7; +#if 0 + ret = device_property_read_u32(dev, "fsync-tx-id", &deserializer_tx_id); // Not necessarily problematic, no frame sync tx found if (ret == -ENODATA || ret == -EINVAL) { dev_info(dev, "Frame sync GPIO tx id not found"); @@ -449,33 +610,58 @@ static int max96717_enable_frame_sync(struct max9x_common *common) return ret; } - ret = regmap_write(map, MAX96717_GPIO_C(8), deserializer_tx_id); + ret = device_property_read_u32(dev, "fsync-gpio", &fsync_gpio); + if (ret && ret != -EINVAL && ret != -ENODATA) { + dev_err(dev, "Failed to read frame sync GPIO with err %d",ret); + return ret; + } + + if (fsync_gpio >= MAX96717_NUM_GPIO) { + dev_err(dev, "Invalid frame sync GPIO %u", fsync_gpio); + return -EINVAL; + } +#endif + ret = regmap_update_bits(map, MAX96717_GPIO_C(fsync_gpio), + MAX96717_GPIO_C_RX_ID, + MAX9X_FIELD_PREP(MAX96717_GPIO_C_RX_ID,deserializer_tx_id)); + if (ret) { dev_err(dev, "Failed to write des frame sync id with err: %d", ret); return ret; } - return 0; + + ret = regmap_update_bits(map, MAX96717_GPIO_A(fsync_gpio), + MAX96717_GPIO_A_RES_CFG_FIELD | MAX96717_GPIO_A_TX_EN_FIELD | MAX96717_GPIO_A_RX_EN_FIELD | MAX96717_GPIO_A_OUT_DIS_FIELD, + MAX96717_GPIO_A_RES_CFG_FIELD | MAX96717_GPIO_A_RX_EN_FIELD); + if (ret) + dev_err(dev, "Failed to enable frame sync GPIO%u RX: %d",fsync_gpio, ret); + + return ret; + } static int max96717_get_datatype(struct max9x_common *common) { - struct device_node *node = common->dev->of_node; struct device *dev = common->dev; struct regmap *map = common->map; - int ret, datatype; + unsigned int datatype = 0; - ret = of_property_read_u32(node, "data-type", &datatype); - if (ret == -ENODATA || ret == -EINVAL) { + for (unsigned int i = 0; i < common->num_video_pipes; i++) { + struct max9x_serdes_video_pipe *pipe = &common->video_pipe[i]; + + if (!pipe->enabled || !pipe->config.num_data_types) + continue; + + datatype = pipe->config.data_type[0]; + break; + } + + if (!datatype) { dev_dbg(dev, "Data-type not found not filtering"); return regmap_write(map, MAX96717_FRONTTOP_16, 0); } - // Other errors are problematic - else if (ret < 0) { - dev_err(dev, "Problem reading in data-type with err %d", ret); - return ret; - } dev_dbg(dev, "Setting image data type to %x", datatype); /* Filter out metadata, only use the image datatype. @@ -491,6 +677,11 @@ static int max96717_enable(struct max9x_common *common) struct device *dev = common->dev; int ret; + dev_dbg(dev, "setup mandatory registers"); + ret = max96717_mandatory_init(common); + if (ret) + return ret; + dev_dbg(dev, "setup gpio"); ret = max96717_setup_gpio(common); if (ret) @@ -515,6 +706,102 @@ static int max96717_enable(struct max9x_common *common) return 0; } + +static int max96717_add_translate_addr(struct max9x_common *common, + unsigned int i2c_id, unsigned int virt_addr, + unsigned int phys_addr) +{ + struct device *dev = common->dev; + struct regmap *map = common->map; + unsigned int alias; + unsigned int src; + int virt_slot = -1; + int phys_slot = -1; + int ret; + + for (alias = 0; alias < MAX96717_NUM_ALIASES; alias++) { + TRY(ret, regmap_read_retry(map, MAX96717_I2C_SRC(i2c_id, alias), &src)); + + src = FIELD_GET(MAX96717_I2C_SRC_FIELD, src); + if (src == virt_addr) { + virt_slot = alias; + break; + } + + if (src == 0 && virt_slot < 0) + virt_slot = alias; + } + + if (virt_slot < 0) + return -ENOSPC; + + dev_dbg(dev, "SRC %02x = %02x, DST %02x = %02x", + MAX96717_I2C_SRC(i2c_id, virt_slot), virt_addr, + MAX96717_I2C_DST(i2c_id, virt_slot), phys_addr); + TRY(ret, regmap_write_retry(map, MAX96717_I2C_DST(i2c_id, virt_slot), + MAX9X_FIELD_PREP(MAX96717_I2C_DST_FIELD, phys_addr))); + TRY(ret, regmap_write_retry(map, MAX96717_I2C_SRC(i2c_id, virt_slot), + MAX9X_FIELD_PREP(MAX96717_I2C_SRC_FIELD, virt_addr))); + + if (virt_addr == phys_addr) + return 0; + + for (alias = 0; alias < MAX96717_NUM_ALIASES; alias++) { + if (alias == virt_slot) + continue; + + TRY(ret, regmap_read_retry(map, MAX96717_I2C_SRC(i2c_id, alias), &src)); + + src = FIELD_GET(MAX96717_I2C_SRC_FIELD, src); + if (src == phys_addr) { + phys_slot = alias; + break; + } + + if ((src == 0 || src == virt_addr) && phys_slot < 0) + phys_slot = alias; + } + + if (phys_slot < 0) { + dev_warn(dev, "No free I2C alias slot to mask physical address 0x%02x", phys_addr); + return 0; + } + + dev_dbg(dev, "SRC %02x = %02x, DST %02x = 00", + MAX96717_I2C_SRC(i2c_id, phys_slot), phys_addr, + MAX96717_I2C_DST(i2c_id, phys_slot)); + TRY(ret, regmap_write_retry(map, MAX96717_I2C_DST(i2c_id, phys_slot), 0)); + TRY(ret, regmap_write_retry(map, MAX96717_I2C_SRC(i2c_id, phys_slot), + MAX9X_FIELD_PREP(MAX96717_I2C_SRC_FIELD, phys_addr))); + + return 0; +} + +static int max96717_remove_translate_addr(struct max9x_common *common, + unsigned int i2c_id, unsigned int virt_addr, + unsigned int phys_addr) +{ + struct regmap *map = common->map; + unsigned int alias; + unsigned int src; + int ret; + + for (alias = 0; alias < MAX96717_NUM_ALIASES; alias++) { + TRY(ret, regmap_read_retry(map, MAX96717_I2C_SRC(i2c_id, alias), &src)); + src = FIELD_GET(MAX96717_I2C_SRC_FIELD, src); + if (src == virt_addr) + return regmap_write_retry(map, MAX96717_I2C_DST(i2c_id, alias), + MAX9X_FIELD_PREP(MAX96717_I2C_DST_FIELD, 0)); + } + + return 0; +} + +static struct max9x_translation_ops max96717_translation_ops = { + .add = max96717_add_translate_addr, + .remove = max96717_remove_translate_addr, +}; + int max96717_get_ops(struct max9x_common_ops **common_ops, struct max9x_serial_link_ops **serial_ops, struct max9x_csi_link_ops **csi_ops, struct max9x_line_fault_ops **lf_ops, struct max9x_translation_ops **trans_ops) diff --git a/drivers/media/i2c/max9x/max96717.h b/drivers/media/i2c/max9x/max96717.h index 022340a7..589501a3 100644 --- a/drivers/media/i2c/max9x/max96717.h +++ b/drivers/media/i2c/max9x/max96717.h @@ -40,8 +40,27 @@ enum max96717_gpio_pull_updn_sel { #define MAX96717_NUM_SERIAL_LINKS 1 #define MAX96717_NUM_VIDEO_PIPES 1 #define MAX96717_NUM_MIPI_MAPS 1 -#define MAX96717_NUM_CSI_LINKS 1 +#define MAX96717_NUM_CSI_LINKS 2 #define MAX96717_NUM_GPIO 11 +#define MAX96717_NUM_DATA_TYPES 4 + +#define MAX96717_PIPE_Z(pipe_id) ((pipe_id) + 2) + +#define MAX96717_REG2 (0x2) +#define MAX96717_REG2_VID_TX_EN_FIELD(pipe_id) BIT(MAX96717_PIPE_Z(pipe_id) + 4) + +#define MAX96717_VIDEO_TX0(pipe_id) (0x100 + MAX96717_PIPE_Z(pipe_id) * 8) +#define MAX96717_VIDEO_TX0_AUTO_BPP_EN_FIELD BIT(3) +#define MAX96717_VIDEO_TX1(pipe_id) (0x101 + MAX96717_PIPE_Z(pipe_id) * 8) +#define MAX96717_VIDEO_TX1_BPP_FIELD GENMASK(5, 0) +#define MAX96717_VIDEO_TX2(pipe_id) (0x102 + MAX96717_PIPE_Z(pipe_id) * 8) +#define MAX96717_VIDEO_TX2_DRIFT_DET_EN_FIELD BIT(1) +#define MAX96717_TX3(pipe_id) (0x53 + MAX96717_PIPE_Z(pipe_id) * 4) +#define MAX96717_TX3_TX_STR_SEL_FIELD GENMASK(1, 0) + +#define MAX96717_CMU2 (0x302) +#define MAX96717_CMU2_PFDDIV_RSHORT_FIELD GENMASK(6, 4) +#define MAX96717_CMU2_PFDDIV_RSHORT_1_1V 1 #define MAX96717_GPIO(gpio) (0x2BE + ((gpio) * 3)) #define MAX96717_GPIO_A(gpio) (MAX96717_GPIO(gpio) + 0) @@ -64,15 +83,18 @@ enum max96717_gpio_pull_updn_sel { * in spite of the max96717 only having 1 video pipe */ #define MAX96717_FRONTTOP_0 (0x308) -#define MAX96717_FRONTTOP_0_SEL_CSI_FIELD(pipe_id) BIT(pipe_id + 2) -#define MAX96717_FRONTTOP_0_START_CSI_FIELD(csi_id) BIT((csi_id) + 5) +#define MAX96717_FRONTTOP_0_SEL_CSI_FIELD(pipe_id) BIT(MAX96717_PIPE_Z(pipe_id)) +#define MAX96717_FRONTTOP_0_START_CSI_FIELD(csi_id) BIT((csi_id) + 4) #define MAX96717_FRONTTOP_9 (0x311) -#define MAX96717_FRONTTOP_9_START_VIDEO_FIELD(pipe_id, csi_id) BIT((pipe_id + 2) + 4 * (csi_id + 1)) +#define MAX96717_FRONTTOP_9_START_VIDEO_FIELD(pipe_id, csi_id) BIT(MAX96717_PIPE_Z(pipe_id) + 4 * (csi_id)) #define MAX96717_FRONTTOP_10 (0x312) #define MAX96717_FRONTTOP_10_DBL8_FIELD(pipe_id) BIT(2) #define MAX96717_FRONTTOP_11 (0x313) #define MAX96717_FRONTTOP_11_DBL10_FIELD(pipe_id) BIT(2) #define MAX96717_FRONTTOP_11_DBL12_FIELD(pipe_id) BIT(6) +#define MAX96717_FRONTTOP_12(pipe_id, dt_slot) (0x314 + MAX96717_PIPE_Z(pipe_id) * 2 + (dt_slot)) +#define MAX96717_MEM_DT_SEL_FIELD GENMASK(5, 0) +#define MAX96717_MEM_DT_EN_FIELD BIT(6) #define MAX96717_FRONTTOP_16 (0x318) #define MAX96717_FRONTTOP_16_FIELD GENMASK(5, 0) #define MAX96717_FRONTTOP_16_ENABLE BIT(6) @@ -81,8 +103,21 @@ enum max96717_gpio_pull_updn_sel { #define MAX96717_FRONTTOP_2X_BPP_FIELD GENMASK(4, 0) #define MAX96717_MIPI_RX (0x330) +#define MAX96717_MIPI_RX_0 (MAX96717_MIPI_RX + 0) +#define MAX96717_MIPI_RX_0_PHY_CFG_FIELD GENMASK(2, 0) +#define MAX96717_MIPI_RX_0_PHY_CFG_A_ONLY 0x4 +#define MAX96717_MIPI_RX_0_PHY_CFG_B_ONLY 0x5 +#define MAX96717_MIPI_RX_0_PHY_CFG_A_AND_B 0x6 +#define MAX96717_MIPI_RX_0_NONCONTCLK_EN_FIELD BIT(6) #define MAX96717_MIPI_RX_1 (MAX96717_MIPI_RX + 1) -#define MAX96717_MIPI_RX_1_SEL_CSI_LANES_FIELD(csi_id) (GENMASK(1, 0) << ((csi_id + 1) * 4)) +#define MAX96717_MIPI_RX_1_SEL_CSI_LANES_FIELD(csi_id) (GENMASK(1, 0) << ((csi_id) * 4)) +#define MAX96717_EXTA(dt_slot) (0x3dc + (dt_slot)) + +#define MAX96717_NUM_ALIASES 2 /* 2 per i2c bus */ +#define MAX96717_I2C_SRC(i2c_id, n) ((i2c_id == 0 ? 0x42 : (0x550 + (4 * ((i2c_id) - 1)))) + (2 * (n)) + 0) +#define MAX96717_I2C_SRC_FIELD GENMASK(7, 1) +#define MAX96717_I2C_DST(i2c_id, n) ((i2c_id == 0 ? 0x42 : (0x550 + (4 * ((i2c_id) - 1)))) + (2 * (n)) + 1) +#define MAX96717_I2C_DST_FIELD GENMASK(7, 1) #define MAX96717_EXT11 (0x383) #define MAX96717_TUNNEL_MODE BIT(7) diff --git a/drivers/media/i2c/max9x/max9x_pdata.h b/drivers/media/i2c/max9x/max9x_pdata.h index 577a9fc6..8a50ac70 100644 --- a/drivers/media/i2c/max9x/max9x_pdata.h +++ b/drivers/media/i2c/max9x/max9x_pdata.h @@ -55,6 +55,7 @@ struct max9x_video_pipe_pdata { // SER unsigned int src_csi_id; + unsigned int stream_id; unsigned int *data_types; unsigned int num_data_types; }; diff --git a/drivers/media/i2c/max9x/serdes.c b/drivers/media/i2c/max9x/serdes.c index 749de59c..8cdd3fdc 100644 --- a/drivers/media/i2c/max9x/serdes.c +++ b/drivers/media/i2c/max9x/serdes.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -29,12 +30,24 @@ #include #include #include +#include #include "serdes.h" #include "regmap-retry.h" #include "media/ipu-acpi-pdata.h" +#define MAX96717F_FORWARD_LINK_FREQ_MHZ 3000 +#define MAX9X_REMOTE_STREAM_COLLECT_MS 80 + +static LIST_HEAD(max9x_des_list); +static DEFINE_MUTEX(max9x_des_list_mutex); + +static bool max9x_acpi_is_max96717f(const char *ser_name) +{ + return ser_name && !strcmp(ser_name, "max96717f"); +} + static const s64 max9x_op_sys_clock[] = { MAX9X_LINK_FREQ_MBPS_TO_HZ(2500), MAX9X_LINK_FREQ_MBPS_TO_HZ(2400), @@ -84,6 +97,7 @@ int max9x_get_ops(char dev_id, rval = max9295_get_ops(common_ops, serial_ops, csi_ops, lf_ops, trans_ops); break; case MAX96717: + case MAX96717F: rval = max96717_get_ops(common_ops, serial_ops, csi_ops, lf_ops, trans_ops); break; default: @@ -117,12 +131,20 @@ static struct max9x_desc max9x_chips[] = { }, /*need to check dev_id and others when used*/ [MAX96717] = { - .dev_id = 0x91, + .dev_id = 0xBF, .rev_reg = 0xE, .serdes_type = MAX9X_SERIALIZER, .chip_type = MAX96717, .get_max9x_ops = max9x_get_ops, }, + [MAX96717F] = { + .dev_id = 0xC8, + .rev_reg = 0xE, + .serdes_type = MAX9X_SERIALIZER, + .chip_type = MAX96717F, + .get_max9x_ops = max9x_get_ops, + }, + }; static const struct of_device_id max9x_of_match[] = { @@ -130,6 +152,7 @@ static const struct of_device_id max9x_of_match[] = { { .compatible = "max9x,max96724", .data = &max9x_chips[MAX96724] }, { .compatible = "max9x,max9295", .data = &max9x_chips[MAX9295] }, { .compatible = "max9x,max96717", .data = &max9x_chips[MAX96717] }, + { .compatible = "max9x,max96717f", .data = &max9x_chips[MAX96717F] }, {} }; MODULE_DEVICE_TABLE(of, max9x_of_match); @@ -140,6 +163,7 @@ static const struct i2c_device_id max9x_id[] = { { "max96724", MAX96724 }, { "max9295", MAX9295 }, { "max96717", MAX96717 }, + { "max96717f", MAX96717F }, { } }; MODULE_DEVICE_TABLE(i2c, max9x_id); @@ -181,6 +205,10 @@ static int max9x_parse_subdev_pdata(struct max9x_common *common, struct max9x_su static int max9x_select_i2c_chan(struct i2c_mux_core *muxc, u32 chan_id); static int max9x_deselect_i2c_chan(struct i2c_mux_core *muxc, u32 chan_id); +static int max9x_i2c_atr_init(struct max9x_common *common); +static void max9x_i2c_atr_deinit(struct max9x_common *common); +static struct i2c_adapter *max9x_link_adapter(struct max9x_common *common, + unsigned int link_id); static int max9x_des_isolate_serial_link(struct max9x_common *common, unsigned int link_id); static int max9x_des_deisolate_serial_link(struct max9x_common *common, unsigned int link_id); @@ -240,6 +268,19 @@ static struct max9x_pdata *pdata_sensor(struct device *dev, struct max9x_subdev_ return NULL; } +static bool max9x_is_max96717_serializer(struct max9x_common *common) +{ + return common->type == MAX9X_SERIALIZER && common->des && + (common->des->chip_type == MAX96717 || + common->des->chip_type == MAX96717F); +} + +static bool max9x_is_max96717f_serializer(struct max9x_common *common) +{ + return common->type == MAX9X_SERIALIZER && common->des && + common->des->chip_type == MAX96717F; +} + static struct max9x_pdata *parse_ser_pdata(struct device *dev, const char *ser_name, char *suffix, unsigned int ser_nlanes, unsigned int phys_addr, unsigned int virt_addr, struct max9x_subdev_pdata *ser_sdinfo, @@ -276,6 +317,7 @@ static struct max9x_pdata *parse_ser_pdata(struct device *dev, const char *ser_n ser_video_pipe->serial_link_id = 0; ser_video_pipe->pipe_id = ser_sdinfo->serial_link_id; ser_video_pipe->src_csi_id = 1; /* PHY B typically */ + ser_video_pipe->stream_id = ser_sdinfo->serial_link_id; ser_video_pipe->num_data_types = 1; ser_video_pipe->data_types = devm_kzalloc(dev, @@ -366,6 +408,16 @@ static void *parse_serdes_pdata(struct device *dev) if (!des_pdata->video_pipes) return NULL; + bool force_group_3gbps = false; + for (unsigned int i = 0; i < des_pdata->num_serial_links; i++) { + struct serdes_subdev_info *sdinfo = &serdes_pdata->subdev_info[i]; + + if (max9x_acpi_is_max96717f(serdes_pdata->ser_name)) { + force_group_3gbps = true; + break; + } + } + for (unsigned int serial_link_id = 0; serial_link_id < des_pdata->num_serial_links; serial_link_id++) { struct max9x_serial_link_pdata *serial_link = &des_pdata->serial_links[serial_link_id]; unsigned int video_pipe_id = serial_link_id; @@ -384,11 +436,20 @@ static void *parse_serdes_pdata(struct device *dev) serial_link->link_id = serial_link_id; serial_link->link_type = MAX9X_LINK_TYPE_GMSL2; - serial_link->rx_freq_mhz = 6000; + serial_link->rx_freq_mhz = force_group_3gbps ? + MAX96717F_FORWARD_LINK_FREQ_MHZ : 6000; serial_link->tx_freq_mhz = 187; des_video_pipe->serial_link_id = serial_link_id; des_video_pipe->pipe_id = video_pipe_id; + /* + * For MAX9296, src_pipe_id programs RX50/RX51 STR_SEL and + * therefore selects the incoming GMSL stream-id, not the remote + * serializer hardware pipe. Use the generated video pipe ID as + * the stream-id; MAX96717F still uses its only hardware pipe Z + * internally, but transmits this unique stream-id to avoid + * same-DES aggregation conflicts. + */ des_video_pipe->src_pipe_id = video_pipe_id; des_video_pipe->num_maps = 3; des_video_pipe->maps = devm_kzalloc(dev, @@ -543,7 +604,7 @@ static int max9x_remap_serializers_resume(struct max9x_common *common, unsigned dev_err(dev, "Device not present at 0x%02x", phys_addr); goto err_regmap; } else { - dev_info(dev, "DEV_ID before: 0x%02x", val); + dev_dbg(dev, "DEV_ID before: 0x%02x", val); } ret = regmap_write_retry(phys_map, 0x00, (virt_addr & 0x7f) << 1); @@ -560,7 +621,7 @@ static int max9x_remap_serializers_resume(struct max9x_common *common, unsigned dev_err(dev, "Device not present after remap to 0x%02x", virt_addr); goto err_regmap; } else { - dev_info(dev, "DEV_ID after: 0x%02x", val); + dev_dbg(dev, "DEV_ID after: 0x%02x", val); } err_regmap: @@ -769,6 +830,7 @@ int max9x_common_init_i2c_client(struct max9x_common *common, mutex_init(&common->link_mutex); mutex_init(&common->isolate_mutex); + INIT_LIST_HEAD(&common->des_list); common->isolated_link = -1; common->selected_link = -1; @@ -846,6 +908,12 @@ int max9x_common_init_i2c_client(struct max9x_common *common, if (ret) goto err_enable; + if (common->type == MAX9X_DESERIALIZER) { + mutex_lock(&max9x_des_list_mutex); + list_add_tail(&common->des_list, &max9x_des_list); + mutex_unlock(&max9x_des_list_mutex); + } + dev_dbg(dev, "Probe successfully."); goto err_phys_map; @@ -854,7 +922,10 @@ int max9x_common_init_i2c_client(struct max9x_common *common, max9x_disable(common); err_adapters: - i2c_mux_del_adapters(common->muxc); + if (common->atr) + max9x_i2c_atr_deinit(common); + else + i2c_mux_del_adapters(common->muxc); err_phys_map: if (common->phys_map) { @@ -879,6 +950,13 @@ void max9x_destroy(struct max9x_common *common) dev_dbg(common->dev, "Destroy"); + if (common->type == MAX9X_DESERIALIZER) { + mutex_lock(&max9x_des_list_mutex); + if (!list_empty(&common->des_list)) + list_del_init(&common->des_list); + mutex_unlock(&max9x_des_list_mutex); + } + max9x_disable_translations(common); for (link_id = 0; link_id < common->num_serial_links; link_id++) { @@ -897,7 +975,10 @@ void max9x_destroy(struct max9x_common *common) v4l2_subdev_cleanup(&common->v4l.sd); media_entity_cleanup(&common->v4l.sd.entity); - i2c_mux_del_adapters(common->muxc); + if (common->atr) + max9x_i2c_atr_deinit(common); + else + i2c_mux_del_adapters(common->muxc); mutex_destroy(&common->link_mutex); mutex_destroy(&common->isolate_mutex); for (int i = 0; i < common->num_csi_links; i++) { @@ -1100,12 +1181,30 @@ static int max9x_get_chip_type(unsigned int dev_id) return -1; } +static const struct max9x_desc *max9x_get_matched_desc(struct i2c_client *client) +{ + const struct of_device_id *of_id; + const struct i2c_device_id *id; + + of_id = of_match_device(max9x_of_match, &client->dev); + if (of_id) + return of_id->data; + + id = i2c_match_id(max9x_id, client); + if (id && id->driver_data) + return &max9x_chips[id->driver_data]; + + return NULL; +} + + int max9x_verify_devid(struct max9x_common *common) { struct device *dev = common->dev; struct regmap *map = common->map; struct regmap *phys_map = common->phys_map; unsigned int dev_id, dev_rev; + const struct max9x_desc *matched_desc; int chip_type, ret; /* @@ -1124,13 +1223,23 @@ int max9x_verify_devid(struct max9x_common *common) } else return ret; } + matched_desc = max9x_get_matched_desc(common->client); + if (matched_desc) { + if (dev_id != matched_desc->dev_id) { + dev_warn(dev, "Chip ID 0x%x does not match expected ID 0x%x", + dev_id, matched_desc->dev_id); + return -EINVAL; + } + common->des = matched_desc; + } else { + chip_type = max9x_get_chip_type(dev_id); + if (chip_type < 0) { + dev_warn(dev, "Unknown chip ID 0x%x", dev_id); + return -EINVAL; + } + common->des = &max9x_chips[chip_type]; + } - chip_type = max9x_get_chip_type(dev_id); - if (chip_type < 0) { - dev_warn(dev, "Unknown chip ID 0x%x", dev_id); - return -EINVAL; - } - common->des = &max9x_chips[chip_type]; common->type = common->des->serdes_type; TRY(ret, regmap_read_retry(map, common->des->rev_reg, &dev_rev)); dev_rev = FIELD_GET(MAX9X_DEV_REV_FIELD, dev_rev); @@ -1201,7 +1310,7 @@ int max9x_remap_serializers(struct max9x_common *common, unsigned int link_id) dev_err(common->dev, "Device not present at 0x%02x", phys_addr); goto err_virt_regmap; } else { - dev_info(common->dev, "DEV_ID before: 0x%02x", val); + dev_dbg(common->dev, "DEV_ID before: 0x%02x", val); } ret = regmap_write_retry(phys_map, 0x00, (virt_addr & 0x7f) << 1); @@ -1218,7 +1327,7 @@ int max9x_remap_serializers(struct max9x_common *common, unsigned int link_id) dev_err(common->dev, "Device not present after remap to 0x%02x", virt_addr); goto err_virt_regmap; } else { - dev_info(common->dev, "DEV_ID after: 0x%02x", val); + dev_dbg(common->dev, "DEV_ID after: 0x%02x", val); } err_virt_regmap: @@ -1239,6 +1348,171 @@ int max9x_remap_serializers(struct max9x_common *common, unsigned int link_id) return ret; } +static int max9x_i2c_atr_attach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client, + u16 alias) +{ + struct max9x_common *common = i2c_atr_get_driver_data(atr); + struct max9x_pdata *pdata = common->dev->platform_data; + unsigned int phys_addr = client->addr; + + if (chan_id >= common->num_serial_links) + return -EINVAL; + + dev_dbg(common->dev, "ATR attach chan %u client 0x%02x alias 0x%02x", + chan_id, client->addr, alias); + + if (pdata) { + for (unsigned int i = 0; i < pdata->num_subdevs; i++) { + struct max9x_subdev_pdata *subdev_pdata = &pdata->subdevs[i]; + + if (subdev_pdata->board_info.addr == client->addr) { + phys_addr = subdev_pdata->phys_addr ? + subdev_pdata->phys_addr : client->addr; + break; + } + } + } + + if (common->type == MAX9X_SERIALIZER && common->translation_ops && + common->translation_ops->add) + return common->translation_ops->add(common, 0, alias, + phys_addr); + + return 0; +} + +static void max9x_i2c_atr_detach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client) +{ + struct max9x_common *common = i2c_atr_get_driver_data(atr); + struct max9x_pdata *pdata = common->dev->platform_data; + unsigned int phys_addr = client->addr; + + if (chan_id >= common->num_serial_links) + return; + + dev_dbg(common->dev, "ATR detach chan %u client 0x%02x", + chan_id, client->addr); + + if (pdata) { + for (unsigned int i = 0; i < pdata->num_subdevs; i++) { + struct max9x_subdev_pdata *subdev_pdata = &pdata->subdevs[i]; + + if (subdev_pdata->board_info.addr == client->addr) { + phys_addr = subdev_pdata->phys_addr ? + subdev_pdata->phys_addr : client->addr; + break; + } + } + } + + if (common->type == MAX9X_SERIALIZER && common->translation_ops && + common->translation_ops->remove) + common->translation_ops->remove(common, 0, client->addr, + phys_addr); +} + +static int max9x_i2c_atr_select(struct i2c_atr *atr, u32 chan_id) +{ + struct max9x_common *common = i2c_atr_get_driver_data(atr); + + return max9x_select_i2c_chan(common->muxc, chan_id); +} + +static void max9x_i2c_atr_deselect(struct i2c_atr *atr, u32 chan_id) +{ + struct max9x_common *common = i2c_atr_get_driver_data(atr); + + max9x_deselect_i2c_chan(common->muxc, chan_id); +} + +static const struct i2c_atr_ops max9x_i2c_atr_ops = { + .attach_client = max9x_i2c_atr_attach_client, + .detach_client = max9x_i2c_atr_detach_client, + .select = max9x_i2c_atr_select, + .deselect = max9x_i2c_atr_deselect, +}; + +static void max9x_i2c_atr_deinit(struct max9x_common *common) +{ + unsigned int link_id; + + if (!common->atr) + return; + + for (link_id = 0; link_id < common->num_serial_links; link_id++) + i2c_atr_del_adapter(common->atr, link_id); + + i2c_atr_delete(common->atr); + common->atr = NULL; + + kfree(common->atr_adap); + common->atr_adap = NULL; +} + +static int max9x_i2c_atr_init(struct max9x_common *common) +{ + unsigned int link_id; + int ret; + + if (!common->num_serial_links) + return -ENODEV; + + common->atr_adap = kcalloc(common->num_serial_links, + sizeof(*common->atr_adap), GFP_KERNEL); + if (!common->atr_adap) + return -ENOMEM; + + common->atr = i2c_atr_new(common->client->adapter, common->dev, + &max9x_i2c_atr_ops, + common->num_serial_links); + if (IS_ERR(common->atr)) { + ret = PTR_ERR(common->atr); + common->atr = NULL; + kfree(common->atr_adap); + common->atr_adap = NULL; + return ret; + } + + i2c_atr_set_driver_data(common->atr, common); + + for (link_id = 0; link_id < common->num_serial_links; link_id++) { + if (!common->serial_link[link_id].enabled) + continue; + + ret = i2c_atr_add_adapter(common->atr, link_id, common->dev, + NULL); + if (ret) + goto err_deinit; + + common->atr_adap[link_id] = + i2c_atr_get_adapter(common->atr, link_id); + if (!common->atr_adap[link_id]) { + ret = -ENODEV; + goto err_deinit; + } + } + + dev_info(common->dev, "Using I2C ATR child adapters"); + + return 0; + +err_deinit: + max9x_i2c_atr_deinit(common); + return ret; +} + +static struct i2c_adapter *max9x_link_adapter(struct max9x_common *common, + unsigned int link_id) +{ + if (common->atr_adap && link_id < common->num_serial_links && + common->atr_adap[link_id]) + return common->atr_adap[link_id]; + + return common->muxc->adapter[link_id]; +} + int max9x_create_adapters(struct max9x_common *common) { struct device *dev = common->dev; @@ -1248,7 +1522,10 @@ int max9x_create_adapters(struct max9x_common *common) unsigned int ms; int err = 0; - for (link_id = 0; link_id < common->num_serial_links; link_id++) { + for (unsigned int link_idx = 0; link_idx < common->num_serial_links; link_idx++) { + link_id = (common->type == MAX9X_DESERIALIZER) ? + (common->num_serial_links - 1 - link_idx) : link_idx; + err = max9x_sysfs_create_get_link(common, link_id); if (err) { dev_err(dev, "failed to create sysfs lock status file for link %d", @@ -1293,6 +1570,13 @@ int max9x_create_adapters(struct max9x_common *common) common->serial_link[link_id].enabled = false; } + err = max9x_i2c_atr_init(common); + if (!err) + return 0; + + dev_warn(dev, "I2C ATR init failed (%d), falling back to legacy mux", + err); + for (link_id = 0; link_id < common->num_serial_links; link_id++) { max9x_setup_translations(common); @@ -1312,6 +1596,7 @@ static void max9x_des_s_csi_link(struct max9x_common *common, unsigned int serial_link_id, int enable) { unsigned int video_pipe_id; + unsigned long handled_csi = 0; int err = 0; for (video_pipe_id = 0; video_pipe_id < common->num_video_pipes; @@ -1341,6 +1626,17 @@ static void max9x_des_s_csi_link(struct max9x_common *common, if (common->csi_link[csi_link_id].config.auto_start) continue; /* Already started at probe */ + if (csi_link_id >= BITS_PER_LONG) { + dev_warn(common->dev, + "Too many CSI links to track %u", + csi_link_id); + continue; + } + + if (handled_csi & BIT(csi_link_id)) + continue; + handled_csi |= BIT(csi_link_id); + if (enable && !video_pipe->config.map[map_id].is_csi_enabled) { if (common->csi_link_ops->enable) { err = common->csi_link_ops->enable( @@ -1391,17 +1687,14 @@ static int _max9x_s_remote_stream(struct max9x_common *common, u32 sink_pad, return -ENODEV; } - if (common->type == MAX9X_DESERIALIZER) { - ret = enable ? v4l2_subdev_enable_streams(remote_sd, - remote_pad->index, - BIT(sink_stream)) : - v4l2_subdev_disable_streams(remote_sd, - remote_pad->index, - BIT(sink_stream)); - - } else { - ret = v4l2_subdev_call(remote_sd, video, s_stream, enable); - } + ret = enable ? v4l2_subdev_enable_streams(remote_sd, + remote_pad->index, + BIT(sink_stream)) : + v4l2_subdev_disable_streams(remote_sd, + remote_pad->index, + BIT(sink_stream)); + if (ret == -EALREADY) + return 0; if (ret) { dev_err(common->dev, "Failed to %s stream %s %u:%u", @@ -1413,11 +1706,146 @@ static int _max9x_s_remote_stream(struct max9x_common *common, u32 sink_pad, return ret; } -static int _max9x_des_set_stream(struct max9x_common *common, u32 sink_pad, - u32 sink_stream, int enable) +static int max9x_des_get_remote_source_stream(struct max9x_common *common, + unsigned int link_id, + u32 *source_stream) +{ + struct v4l2_subdev_state *remote_state; + struct v4l2_subdev *remote_sd; + struct v4l2_subdev_route *route; + u32 sink_pad = max9x_serial_link_to_pad(common, link_id); + struct media_pad *remote_pad; + + if (sink_pad >= common->v4l.num_pads) + return -EINVAL; + + remote_pad = media_pad_remote_pad_first(&common->v4l.pads[sink_pad]); + if (IS_ERR_OR_NULL(remote_pad)) + return IS_ERR(remote_pad) ? PTR_ERR(remote_pad) : -ENODEV; + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + if (!remote_sd) + return -ENODEV; + + remote_state = v4l2_subdev_lock_and_get_active_state(remote_sd); + for_each_active_route(&remote_state->routing, route) { + if (route->source_pad == remote_pad->index) { + *source_stream = route->source_stream; + v4l2_subdev_unlock_state(remote_state); + return 0; + } + } + v4l2_subdev_unlock_state(remote_state); + + /* + * If userspace has not programmed the remote serializer route yet, + * preserve the current two-link mapping: link A -> stream 0, + * link B -> stream 1. + */ + *source_stream = link_id; + return 0; +} + +static int max9x_des_set_remote_stream(struct max9x_common *common, + unsigned int link_id, int enable) +{ + u32 sink_pad = max9x_serial_link_to_pad(common, link_id); + u32 remote_stream; + int ret; + + if (sink_pad >= common->v4l.num_pads) + return -EINVAL; + + ret = max9x_des_get_remote_source_stream(common, link_id, + &remote_stream); + if (ret) + return ret; + + if (enable && (common->v4l.remote_stream_enabled & BIT(link_id))) + return 0; + + if (!enable && !(common->v4l.remote_stream_enabled & BIT(link_id))) + return 0; + + ret = max9x_des_isolate_serial_link(common, link_id); + if (ret) + return ret; + + ret = _max9x_s_remote_stream(common, sink_pad, remote_stream, enable); + if (ret) + goto out_deisolate; + + if (enable) + common->v4l.remote_stream_enabled |= BIT(link_id); + else + common->v4l.remote_stream_enabled &= ~BIT(link_id); + +out_deisolate: + if (max9x_des_deisolate_serial_link(common, link_id) && !ret) + ret = -EIO; + + return ret; +} + +static int max9x_des_set_all_remote_streams(struct max9x_common *common, + int enable) +{ + unsigned long changed = 0; + int ret = 0; + + for (unsigned int link_id = 0; link_id < common->num_serial_links; + link_id++) { + if (!common->serial_link[link_id].enabled) + continue; + + ret = max9x_des_set_remote_stream(common, link_id, enable); + if (ret) + goto rollback; + + changed |= BIT(link_id); + } + + return 0; + +rollback: + if (enable) { + for (unsigned int link_id = 0; link_id < common->num_serial_links; + link_id++) { + if (changed & BIT(link_id)) + max9x_des_set_remote_stream(common, link_id, + false); + } + } + + return ret; +} + +static int max9x_des_wait_remote_stream_start(struct max9x_common *common) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(10000); + + do { + mutex_lock(&max9x_des_list_mutex); + if (!common->v4l.remote_stream_starting) { + mutex_unlock(&max9x_des_list_mutex); + return 0; + } + mutex_unlock(&max9x_des_list_mutex); + + usleep_range(1000, 1050); + } while (time_is_after_jiffies(timeout)); + + dev_err(common->dev, "Timed out waiting for remote stream start"); + return -ETIMEDOUT; +} + +static int _max9x_des_set_stream(struct max9x_common *common, + u32 sink_pad, u32 sink_stream, int enable) { int rxport = 0; int ret = 0; + bool start_all_remote_streams = false; + bool stream_registered = false; if (sink_pad >= common->v4l.num_pads) return -EINVAL; @@ -1428,21 +1856,86 @@ static int _max9x_des_set_stream(struct max9x_common *common, u32 sink_pad, sink_pad); return -EINVAL; } - if (enable) + if (enable) { + if (common->atr) { + mutex_lock(&max9x_des_list_mutex); + if (!common->v4l.ref_count && + !common->v4l.remote_stream_starting) { + common->v4l.remote_stream_starting = true; + start_all_remote_streams = true; + } + common->v4l.ref_count++; + stream_registered = true; + mutex_unlock(&max9x_des_list_mutex); + + if (start_all_remote_streams) { + msleep(MAX9X_REMOTE_STREAM_COLLECT_MS); + ret = max9x_des_set_all_remote_streams(common, + true); + mutex_lock(&max9x_des_list_mutex); + common->v4l.remote_stream_starting = false; + mutex_unlock(&max9x_des_list_mutex); + } else { + ret = max9x_des_wait_remote_stream_start(common); + if (!ret && + !(common->v4l.remote_stream_enabled & + BIT(rxport))) + ret = max9x_des_set_remote_stream(common, + rxport, + true); + } + } else { + ret = _max9x_s_remote_stream(common, sink_pad, + sink_stream, enable); + if (!ret) { + common->v4l.ref_count++; + stream_registered = true; + } + } + + if (ret) { + dev_err(common->dev, + "Failed to enable remote stream for sink %s %u:%u", + common->v4l.sd.entity.name, sink_pad, sink_stream); + if (common->atr) { + mutex_lock(&max9x_des_list_mutex); + if (stream_registered && + common->v4l.ref_count > 0) + common->v4l.ref_count--; + mutex_unlock(&max9x_des_list_mutex); + } + return ret; + } + + max9x_des_s_csi_link(common, rxport, enable); + } else { max9x_des_s_csi_link(common, rxport, enable); - ret = _max9x_s_remote_stream(common, sink_pad, sink_stream, enable); - if (ret) { - dev_err(common->dev, - "Failed to %s remote stream for sink %s %u:%u", - enable ? "enable" : "disable", - common->v4l.sd.entity.name, sink_pad, sink_stream); - return ret; + if (common->atr) { + mutex_lock(&max9x_des_list_mutex); + if (common->v4l.ref_count > 0) + common->v4l.ref_count--; + if (!common->v4l.ref_count) + ret = max9x_des_set_all_remote_streams(common, + false); + mutex_unlock(&max9x_des_list_mutex); + } else { + if (common->v4l.ref_count > 0) + common->v4l.ref_count--; + ret = _max9x_s_remote_stream(common, sink_pad, + sink_stream, enable); + } + if (ret) { + dev_err(common->dev, + "Failed to disable remote stream for sink %s %u:%u", + common->v4l.sd.entity.name, sink_pad, sink_stream); + if (common->v4l.ref_count > 0) + max9x_des_s_csi_link(common, rxport, true); + return ret; + } } - if (!enable) - max9x_des_s_csi_link(common, rxport, enable); - return 0; + return ret; } static int _max9x_ser_set_stream(struct max9x_common *common, u32 sink_pad, @@ -1741,21 +2234,25 @@ static int max9x_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { struct max9x_common *common = max9x_sd_to_common(sd); + int des_sink_pad = max9x_serial_link_to_pad(common, 0); + int des_source_pad = max9x_csi_link_to_pad(common, 0); + int ser_sink_pad = max9x_csi_link_to_pad(common, 0); + int ser_source_pad = max9x_serial_link_to_pad(common, 0); struct v4l2_subdev_route des_routes[] = { { - .sink_pad = 5, + .sink_pad = des_sink_pad, .sink_stream = 0, - .source_pad = 0, + .source_pad = des_source_pad, .source_stream = 0, .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, }, }; struct v4l2_subdev_route ser_routes[] = { { - .sink_pad = 0, + .sink_pad = ser_sink_pad, .sink_stream = 0, - .source_pad = 2, + .source_pad = ser_source_pad, .source_stream = 0, .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, }, @@ -1769,6 +2266,14 @@ static int max9x_init_state(struct v4l2_subdev *sd, .routes = ser_routes, }; + if (common->type == MAX9X_DESERIALIZER && + (des_sink_pad < 0 || des_source_pad < 0)) + return -EINVAL; + + if (common->type == MAX9X_SERIALIZER && + (ser_sink_pad < 0 || ser_source_pad < 0)) + return -EINVAL; + if (common->type == MAX9X_DESERIALIZER) return _max9x_set_routing(sd, state, &des_routing); else @@ -1795,6 +2300,12 @@ static int max9x_registered(struct v4l2_subdev *sd) struct device *dev = common->dev; int ret; + dev_dbg(dev, "registered callback: type=%d links=%d subdevs=%d atr=%p", + common->type, common->num_serial_links, + common->dev->platform_data ? + ((struct max9x_pdata *)common->dev->platform_data)->num_subdevs : -1, + common->atr); + for (unsigned int link_id = 0; link_id < common->num_serial_links; link_id++) { if (!common->serial_link[link_id].enabled) { dev_dbg(dev, "Serial-link %d not enabled, skipping subdevs", link_id); @@ -1808,29 +2319,32 @@ static int max9x_registered(struct v4l2_subdev *sd) if (subdev_pdata) { struct max9x_pdata *ser_pdata = subdev_pdata->board_info.platform_data; + struct i2c_adapter *adapter = + max9x_link_adapter(common, link_id); struct v4l2_subdev *subdev = NULL; WARN_ON(ser_pdata->num_serial_links < 1); ser_pdata->serial_links[0].des_client = common->client; ser_pdata->serial_links[0].des_link_id = link_id; + if (!adapter) + return -ENODEV; + /* - * Isolate this link until after reset and potential address remapping, - * avoiding a race condition with two serializers resetting same - * physical i2c at the same time + * Initial serializer probe/remap still needs an isolated + * MAX9296 link because both remote serializers share the + * same physical address before remap. ATR is used as the + * adapter, so later child I2C can avoid legacy mux select. */ ret = max9x_des_isolate_serial_link(common, link_id); if (!ret) subdev = v4l2_i2c_new_subdev_board( - sd->v4l2_dev, - common->muxc->adapter[link_id], - &subdev_pdata->board_info, - NULL); + sd->v4l2_dev, adapter, + &subdev_pdata->board_info, NULL); ret = max9x_des_deisolate_serial_link(common, link_id); if (ret) return ret; - if (IS_ERR_OR_NULL(subdev)) { dev_err(dev, "Failure registering serializer %s (0x%02x)", subdev_pdata->board_info.type, @@ -1869,12 +2383,16 @@ static int max9x_registered(struct v4l2_subdev *sd) if (subdev_pdata->serial_link_id == link_id) { char dev_id[I2C_NAME_SIZE]; + struct i2c_adapter *adapter = + max9x_link_adapter(common, link_id); snprintf(dev_id, sizeof(dev_id), "i2c-%s", subdev_pdata->board_info.dev_name); - dev_dbg(dev, "Registering sensor %s (%s)...", - subdev_pdata->board_info.type, dev_id); + dev_dbg(dev, "Registering sensor %s (%s) on link %u adapter %s", + subdev_pdata->board_info.type, + dev_id, link_id, + adapter ? adapter->name : ""); struct gpiod_lookup_table *sensor_gpios; @@ -1901,7 +2419,7 @@ static int max9x_registered(struct v4l2_subdev *sd) struct v4l2_subdev *subdev = v4l2_i2c_new_subdev_board(sd->v4l2_dev, - common->muxc->adapter[link_id], + adapter, &subdev_pdata->board_info, NULL); gpiod_remove_lookup_table(sensor_gpios); @@ -1924,8 +2442,8 @@ static int max9x_registered(struct v4l2_subdev *sd) PAD_SIGNAL_DEFAULT); int local_pad = max9x_csi_link_to_pad(common, 0); - dev_dbg(dev, "Create link from sen pad %d -> ser link %d (pad %d)", - remote_pad, link_id, + dev_dbg(dev, "Create link from sen pad %d -> ser csi 0 (pad %d)", + remote_pad, local_pad); ret = media_create_pad_link(&subdev->entity, remote_pad, @@ -2335,7 +2853,6 @@ static int max9x_parse_serial_link_pdata(struct max9x_common *common, { struct device *dev = common->dev; unsigned int serial_link_id = serial_link_pdata->link_id; - if (serial_link_id >= common->num_serial_links) { dev_err(dev, "Serial link pdata: Invalid link id"); return -EINVAL; @@ -2349,6 +2866,13 @@ static int max9x_parse_serial_link_pdata(struct max9x_common *common, serial_link->config.rx_freq_mhz = serial_link_pdata->rx_freq_mhz; serial_link->config.tx_freq_mhz = serial_link_pdata->tx_freq_mhz; + if (max9x_is_max96717f_serializer(common)) { + serial_link->config.rx_freq_mhz = MAX96717F_FORWARD_LINK_FREQ_MHZ; + serial_link_pdata->rx_freq_mhz = MAX96717F_FORWARD_LINK_FREQ_MHZ; + + } + + if (serial_link_pdata->poc_regulator[0] != 0) { serial_link->poc_regulator = devm_regulator_get_optional(dev, serial_link_pdata->poc_regulator); @@ -2372,6 +2896,10 @@ static int max9x_parse_video_pipe_pdata(struct max9x_common *common, unsigned int max_maps; unsigned int max_data_types; + if (max9x_is_max96717_serializer(common)) + pipe_id = 0; + + if (serial_link_id >= common->num_serial_links) { dev_err(dev, "Video pdata: Invalid serial link id"); return -EINVAL; @@ -2435,6 +2963,7 @@ static int max9x_parse_video_pipe_pdata(struct max9x_common *common, } pipe->config.src_csi = video_pipe_pdata->src_csi_id; + pipe->config.stream_id = video_pipe_pdata->stream_id; for (unsigned int i = 0; i < video_pipe_pdata->num_data_types; i++) { pipe->config.data_type[i] = video_pipe_pdata->data_types[i]; @@ -2507,13 +3036,19 @@ int max9x_select_i2c_chan(struct i2c_mux_core *muxc, u32 chan_id) struct max9x_common *common = i2c_mux_priv(muxc); int ret = 0; unsigned long timeout = jiffies + msecs_to_jiffies(10000); + bool already_isolated = false; if (unlikely(chan_id > common->num_serial_links)) return -EINVAL; do { mutex_lock(&common->isolate_mutex); - if (common->selected_link < 0 || chan_id == common->selected_link) + if (chan_id == common->isolated_link) { + already_isolated = true; + break; + } + + if (common->isolated_link < 0) break; mutex_unlock(&common->isolate_mutex); @@ -2526,11 +3061,13 @@ int max9x_select_i2c_chan(struct i2c_mux_core *muxc, u32 chan_id) } } while (1); - common->selected_link = chan_id; - - if (common->serial_link_ops && common->serial_link_ops->select) + if (!already_isolated && + common->serial_link_ops && common->serial_link_ops->select) ret = common->serial_link_ops->select(common, chan_id); + if (!ret && !already_isolated) + common->selected_link = chan_id; +unlock: mutex_unlock(&common->isolate_mutex); return ret; @@ -2545,10 +3082,12 @@ int max9x_deselect_i2c_chan(struct i2c_mux_core *muxc, u32 chan_id) return -EINVAL; mutex_lock(&common->isolate_mutex); - if (common->serial_link_ops && common->serial_link_ops->deselect) + if (common->selected_link == chan_id && + common->serial_link_ops && common->serial_link_ops->deselect) ret = common->serial_link_ops->deselect(common, chan_id); + if (!ret && common->selected_link == chan_id) + common->selected_link = -1; - common->selected_link = -1; mutex_unlock(&common->isolate_mutex); return ret; @@ -2564,7 +3103,7 @@ int max9x_des_isolate_serial_link(struct max9x_common *common, unsigned int link return -EINVAL; } - dev_info(common->dev, "Isolate %d", link_id); + dev_dbg(common->dev, "Isolate %d", link_id); do { mutex_lock(&common->isolate_mutex); @@ -2589,7 +3128,7 @@ int max9x_des_isolate_serial_link(struct max9x_common *common, unsigned int link ret = common->serial_link_ops->isolate(common, link_id); mutex_unlock(&common->isolate_mutex); - dev_info(common->dev, "Isolate %d complete", link_id); + dev_dbg(common->dev, "Isolate %d complete", link_id); return ret; } @@ -2601,14 +3140,14 @@ int max9x_des_deisolate_serial_link(struct max9x_common *common, unsigned int li if (link_id >= common->num_serial_links) return -EINVAL; - dev_info(common->dev, "Deisolate %d", link_id); + dev_dbg(common->dev, "Deisolate %d", link_id); mutex_lock(&common->isolate_mutex); if (common->serial_link_ops && common->serial_link_ops->deisolate) ret = common->serial_link_ops->deisolate(common, link_id); common->isolated_link = -1; - dev_info(common->dev, "Deisolate %d complete", link_id); + dev_dbg(common->dev, "Deisolate %d complete", link_id); mutex_unlock(&common->isolate_mutex); return ret; @@ -2790,6 +3329,7 @@ static struct i2c_driver max9x_driver = { module_i2c_driver(max9x_driver); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(I2C_ATR); MODULE_AUTHOR("Josh Watts "); MODULE_AUTHOR("Yan, Dongcheng "); MODULE_DESCRIPTION("Common logic for Maxim GMSL serializers & deserializers"); diff --git a/drivers/media/i2c/max9x/serdes.h b/drivers/media/i2c/max9x/serdes.h index 34ee7746..3ab128d8 100644 --- a/drivers/media/i2c/max9x/serdes.h +++ b/drivers/media/i2c/max9x/serdes.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,7 @@ enum max9x_chip_type { MAX96724 = 1, MAX9295, MAX96717, + MAX96717F, MAX96724F, MAX96724R, }; @@ -172,6 +174,7 @@ struct max9x_serdes_pipe_config { // Serializer //TODO: dst_link? unsigned int src_csi; + unsigned int stream_id; unsigned int *data_type; unsigned int num_data_types; //TODO: MIPI VC filter mask @@ -211,6 +214,8 @@ struct max9x_serdes_v4l { struct v4l2_ctrl *link_freq; // CSI link frequency, used to determine ISP clock struct v4l2_mbus_framefmt *ffmts; int ref_count; + unsigned long remote_stream_enabled; + bool remote_stream_starting; }; struct max9x_serdes_csi_link { @@ -246,13 +251,15 @@ struct max9x_serdes_line_fault { }; struct max9x_common { - struct max9x_desc *des; + const struct max9x_desc *des; struct device *dev; struct i2c_client *client; struct regmap *map; struct i2c_client *phys_client; struct regmap *phys_map; struct i2c_mux_core *muxc; + struct i2c_atr *atr; + struct i2c_adapter **atr_adap; struct gpio_chip gpio_chip; enum max9x_serdes_type type; @@ -279,7 +286,7 @@ struct max9x_common { int num_line_faults; struct max9x_serdes_v4l v4l; - + struct list_head des_list; struct mutex link_mutex; struct mutex isolate_mutex; int isolated_link; diff --git a/drivers/media/platform/intel/ipu-acpi-common.c b/drivers/media/platform/intel/ipu-acpi-common.c index a8701c13..dcf92d62 100644 --- a/drivers/media/platform/intel/ipu-acpi-common.c +++ b/drivers/media/platform/intel/ipu-acpi-common.c @@ -446,7 +446,7 @@ int ipu_acpi_get_cam_data(struct device *dev, sensor->pprval = sensor_data.pprval; sensor->pprunit = sensor_data.pprunit; sensor->bus_type = sensor_data.phyconfig; - sensor->degree = sensor_data.degree; + sensor->degree = 90;//sensor_data.degree; pr_info("IPU ACPI: SSDB: name %s. link %d. lanes %d. pprval %d. pprunit %x. degree %d", dev_name(dev), sensor->link, sensor->lanes, sensor->pprval, sensor->pprunit, diff --git a/include/linux/i2c-atr.h b/include/linux/i2c-atr.h new file mode 100644 index 00000000..be76b97b --- /dev/null +++ b/include/linux/i2c-atr.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * I2C Address Translator + * + * Copyright (c) 2019,2022 Luca Ceresoli + * Copyright (c) 2022,2023 Tomi Valkeinen + * + * Based on i2c-mux.h + */ + +#ifndef _LINUX_I2C_ATR_H +#define _LINUX_I2C_ATR_H + +#include +#include + +struct device; +struct fwnode_handle; +struct i2c_atr; + +/** + * struct i2c_atr_ops - Callbacks from ATR to the device driver. + * @attach_client: Notify the driver of a new device connected on a child + * bus, with the alias assigned to it. The driver must + * configure the hardware to use the alias. + * @detach_client: Notify the driver of a device getting disconnected. The + * driver must configure the hardware to stop using the + * alias. + * @select: Optional callback before a transfer on a child bus. + * @deselect: Optional callback after a transfer on a child bus. + * + * All these functions return 0 on success, a negative error code otherwise. + */ +struct i2c_atr_ops { + int (*attach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client, u16 alias); + void (*detach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client); + int (*select)(struct i2c_atr *atr, u32 chan_id); + void (*deselect)(struct i2c_atr *atr, u32 chan_id); +}; + +/** + * i2c_atr_new() - Allocate and initialize an I2C ATR helper. + * @parent: The parent (upstream) adapter + * @dev: The device acting as an ATR + * @ops: Driver-specific callbacks + * @max_adapters: Maximum number of child adapters + * + * The new ATR helper is connected to the parent adapter but has no child + * adapters. Call i2c_atr_add_adapter() to add some. + * + * Call i2c_atr_delete() to remove. + * + * Return: pointer to the new ATR helper object, or ERR_PTR + */ +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters); + +/** + * i2c_atr_delete - Delete an I2C ATR helper. + * @atr: I2C ATR helper to be deleted. + * + * Precondition: all the adapters added with i2c_atr_add_adapter() must be + * removed by calling i2c_atr_del_adapter(). + */ +void i2c_atr_delete(struct i2c_atr *atr); + +/** + * i2c_atr_add_adapter - Create a child ("downstream") I2C bus. + * @atr: The I2C ATR + * @chan_id: Index of the new adapter (0 .. max_adapters-1). This value is + * passed to the callbacks in `struct i2c_atr_ops`. + * @adapter_parent: The device used as the parent of the new i2c adapter, or NULL + * to use the i2c-atr device as the parent. + * @bus_handle: The fwnode handle that points to the adapter's i2c + * peripherals, or NULL. + * + * After calling this function a new i2c bus will appear. Adding and removing + * devices on the downstream bus will result in calls to the + * &i2c_atr_ops->attach_client and &i2c_atr_ops->detach_client callbacks for the + * driver to assign an alias to the device. + * + * The adapter's fwnode is set to @bus_handle, or if @bus_handle is NULL the + * function looks for a child node whose 'reg' property matches the chan_id + * under the i2c-atr device's 'i2c-atr' node. + * + * Call i2c_atr_del_adapter() to remove the adapter. + * + * Return: 0 on success, a negative error code otherwise. + */ +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct device *adapter_parent, + struct fwnode_handle *bus_handle); + +/** + * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by + * i2c_atr_add_adapter(). If no I2C bus has been added + * this function is a no-op. + * @atr: The I2C ATR + * @chan_id: Index of the adapter to be removed (0 .. max_adapters-1) + */ +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id); + +/** + * i2c_atr_set_driver_data - Set private driver data to the i2c-atr instance. + * @atr: The I2C ATR + * @data: Pointer to the data to store + */ +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data); + +/** + * i2c_atr_get_driver_data - Get the stored drive data. + * @atr: The I2C ATR + * + * Return: Pointer to the stored data + */ +void *i2c_atr_get_driver_data(struct i2c_atr *atr); + +/** + * i2c_atr_get_adapter - Return a child adapter added to an ATR helper. + * @atr: The I2C ATR + * @chan_id: Index of the adapter to return + * + * Return: The child adapter for @chan_id, or NULL if it has not been added. + */ +struct i2c_adapter *i2c_atr_get_adapter(struct i2c_atr *atr, u32 chan_id); + +#endif /* _LINUX_I2C_ATR_H */