From patchwork Tue Jan 8 22:39:50 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 10753073 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id BF5FB6C5 for ; Tue, 8 Jan 2019 23:08:33 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AD1BA28D33 for ; Tue, 8 Jan 2019 23:08:33 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9F53328E1E; Tue, 8 Jan 2019 23:08:33 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 88AD528D33 for ; Tue, 8 Jan 2019 23:08:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729333AbfAHXIb (ORCPT ); Tue, 8 Jan 2019 18:08:31 -0500 Received: from hostingweb31-40.netsons.net ([89.40.174.40]:48418 "EHLO hostingweb31-40.netsons.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729281AbfAHXIa (ORCPT ); Tue, 8 Jan 2019 18:08:30 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lucaceresoli.net; s=default; h=References:In-Reply-To:Message-Id:Date: Subject:Cc:To:From:Sender:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=YAvZ/pH6lMIksVLRvTR8eQqY0c+EoHMb/3U4kL6GjKs=; b=yVU8Dnp1mEt9WPCoepziK4bSP oLQnYWbYPNJgWTVMC8rlZGXXg0eo+t1uzLeOcuJ2FJrLK5cZvbzMBjJWCvAnQXuDpRSvrN5hO4ou2 KKbzR/b0nppoJEfp1mlx73icrCKfiJR7HZ3cuVtls6hcxbiBeeIDmgMRMalq/CrajD/7Q=; Received: from [78.134.43.6] (port=50994 helo=melee.fritz.box) by hostingweb31.netsons.net with esmtpa (Exim 4.91) (envelope-from ) id 1gh02Y-00FWab-Eu; Tue, 08 Jan 2019 23:40:06 +0100 From: Luca Ceresoli To: linux-media@vger.kernel.org Cc: Luca Ceresoli , Kieran Bingham , Laurent Pinchart , jacopo mondi , Vladimir Zapolskiy , Wolfram Sang , Peter Rosin , Mauro Carvalho Chehab , Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Luca Ceresoli Subject: [RFC 1/4] i2c: core: let adapters be notified of client attach/detach Date: Tue, 8 Jan 2019 23:39:50 +0100 Message-Id: <20190108223953.9969-2-luca@lucaceresoli.net> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190108223953.9969-1-luca@lucaceresoli.net> References: <20190108223953.9969-1-luca@lucaceresoli.net> X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - hostingweb31.netsons.net X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - lucaceresoli.net X-Get-Message-Sender-Via: hostingweb31.netsons.net: authenticated_id: luca+lucaceresoli.net/only user confirmed/virtual account not confirmed X-Authenticated-Sender: hostingweb31.netsons.net: luca@lucaceresoli.net X-Source: X-Source-Args: X-Source-Dir: Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Luca Ceresoli An adapter might need to know when a new device is about to be added. This will soon bee needed to implement an "I2C address translator" (ATR for short), a device that propagates I2C transactions with a different slave address (an "alias" address). An ATR driver needs to know when a slave is being added to find a suitable alias and program the device translation map. Add an attach/detach callback pair to allow adapter drivers to be notified of clients being added and removed. Signed-off-by: Luca Ceresoli --- drivers/i2c/i2c-core-base.c | 16 ++++++++++++++++ include/linux/i2c.h | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 28460f6a60cc..4f8776e065ce 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -778,6 +778,11 @@ i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) } } + if (adap->attach_ops && + adap->attach_ops->attach_client && + adap->attach_ops->attach_client(adap, info, client) != 0) + goto err_attach_client; + status = device_register(&client->dev); if (status) goto out_free_props; @@ -788,6 +793,9 @@ i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) return client; out_free_props: + if (adap->attach_ops && adap->attach_ops->detach_client) + adap->attach_ops->detach_client(adap, client); +err_attach_client: if (info->properties) device_remove_properties(&client->dev); out_err_put_of_node: @@ -810,9 +818,17 @@ EXPORT_SYMBOL_GPL(i2c_new_device); */ void i2c_unregister_device(struct i2c_client *client) { + struct i2c_adapter *adap; + if (!client) return; + adap = client->adapter; + + if (adap->attach_ops && + adap->attach_ops->detach_client) + adap->attach_ops->detach_client(adap, client); + if (client->dev.of_node) { of_node_clear_flag(client->dev.of_node, OF_POPULATED); of_node_put(client->dev.of_node); diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 65b4eaed1d96..600d136b1056 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -551,6 +551,14 @@ struct i2c_lock_operations { void (*unlock_bus)(struct i2c_adapter *, unsigned int flags); }; +struct i2c_attach_operations { + int (*attach_client)(struct i2c_adapter *, + const struct i2c_board_info *, + struct i2c_client *); + void (*detach_client)(struct i2c_adapter *, + struct i2c_client *); +}; + /** * struct i2c_timings - I2C timing information * @bus_freq_hz: the bus frequency in Hz @@ -674,6 +682,7 @@ struct i2c_adapter { /* data fields that are valid for all devices */ const struct i2c_lock_operations *lock_ops; + const struct i2c_attach_operations *attach_ops; struct rt_mutex bus_lock; struct rt_mutex mux_lock; From patchwork Tue Jan 8 22:39:51 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 10753079 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9C1191399 for ; Tue, 8 Jan 2019 23:08:55 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 88C1328396 for ; Tue, 8 Jan 2019 23:08:55 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 7D14628420; Tue, 8 Jan 2019 23:08:55 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8154C28396 for ; Tue, 8 Jan 2019 23:08:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729192AbfAHXI2 (ORCPT ); Tue, 8 Jan 2019 18:08:28 -0500 Received: from hostingweb31-40.netsons.net ([89.40.174.40]:56336 "EHLO hostingweb31-40.netsons.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728112AbfAHXI2 (ORCPT ); Tue, 8 Jan 2019 18:08:28 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lucaceresoli.net; s=default; h=References:In-Reply-To:Message-Id:Date: Subject:Cc:To:From:Sender:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=SSXpGBvGr92bvnjl6XsytnZAIXJo1Id/u0bu79tzMm8=; b=eKzwW7eIASl2WbNidLJWY8E88 r+DQQ5dzARcn8VFsdRKxPMXC0d0/FlWqLroYCPpIyIIumpyvxbxbX5ztc6C9KeZA9FUZTiwmXPBag Jf9Syt8TfNsZIPPvmeXfOZPsUldj6hta+bQjEFL5agAyfCA343d9qEvq46rOhtpyWTLIw=; Received: from [78.134.43.6] (port=50994 helo=melee.fritz.box) by hostingweb31.netsons.net with esmtpa (Exim 4.91) (envelope-from ) id 1gh02Y-00FWab-Un; Tue, 08 Jan 2019 23:40:07 +0100 From: Luca Ceresoli To: linux-media@vger.kernel.org Cc: Luca Ceresoli , Kieran Bingham , Laurent Pinchart , jacopo mondi , Vladimir Zapolskiy , Wolfram Sang , Peter Rosin , Mauro Carvalho Chehab , Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Luca Ceresoli Subject: [RFC 2/4] i2c: mux: notify client attach/detach, add ATR Date: Tue, 8 Jan 2019 23:39:51 +0100 Message-Id: <20190108223953.9969-3-luca@lucaceresoli.net> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190108223953.9969-1-luca@lucaceresoli.net> References: <20190108223953.9969-1-luca@lucaceresoli.net> X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - hostingweb31.netsons.net X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - lucaceresoli.net X-Get-Message-Sender-Via: hostingweb31.netsons.net: authenticated_id: luca+lucaceresoli.net/only user confirmed/virtual account not confirmed X-Authenticated-Sender: hostingweb31.netsons.net: luca@lucaceresoli.net X-Source: X-Source-Args: X-Source-Dir: Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Luca Ceresoli **** Note! This patch is not 100% complete - see NOTES/TODO below **** Add the "address translator" feature (ATR for short) to i2c-mux. An ATR is a device that looks similar to an i2c-mux: it has an I2C slave "upstream" port and N master "downstream" ports, and forwards transactions from upstream to the appropriate downstream port. But is is different in that the forwarded transaction has a different slave address. The address used on the upstream bus is called the "alias" and is (potentially) different from the physical slave address of the downstream chip. The ATR maintains a translation table mapping each i2c_client to its alias. The table is looked up at every transaction to modify the messages on the fly. After the transaction completes, the old addresses are written back into the messages: this is needed because chip drivers can reuse the same messages without reinitializing them. Choosing the aliases is not done here, but by the user device driver. Note that the ATR hardware can be a part of a more complex chip, such as the TI DS90UB9xx video serializer/deserializer chips which also allow access to remote I2C chips. A typical example follows. Topology: Slave X @ 0x10 .-----. | .-----. | |---+---- B | CPU |--A--| ATR | `-----' | |---+---- C `-----' | Slave Y @ 0x10 Table: Client Alias ------------- X 0x20 Y 0x30 Transaction: - Slave X driver sends a transaction (on adapter B), slave address 0x10 - ATR driver rewrites messages with address 0x20, forwards to adapter A - Physical I2C transaction on bus A, slave address 0x20 - ATR chip propagates transaction on bus B with address translated to 0x10 - Slave X chip replies on bus B - ATR chip forwards reply on bus A - ATR driver rewrites messages with address 0x10 - Slave X driver gets back the msgs[], with reply and address 0x10 Usage: 1. When instantiating the mux core, pass your attadh/detach callbacks to i2c_mux_alloc() 2. When the attach callback is called pick an appropriate alias, configure it in your chip and return the chosen alias in the alias_id parameter 3. When the detach callback is called, deconfigure the alias from your chip and put is back in the pool for later usage NOTES: * I added this feature to i2c-mux, but in the end I realized it is very invasive. Perhaps it's better to create a new i2c-atr.c file with only the ATR features and leave i2c-mux alone. However the bulk of the ATR implementation should not be much different. * This code has been tested only on a limited setup TODO: * Add i2c_mux_del_adapters() variant to delete only one adapter: the DS90UB9xx driver expects to add and remove each downstream adapter independently, i2c-mux can only remove all adapters at once. * Implement remapping also in i2c_mux_smbus_xfer - Perhaps collapse the two master_xfer functions into one (and the same for the smbus_xfer functions) to avoid duplication of the remapping code * Maybe split in 2 commits: one to add attach_ops which is potentially useful on its own, another to add the actual ATR * Update all calls to i2c_mux_alloc (add NULL as last parameter), not only pca954x * Document devicetree binding, new callbacks and I2C_MUX_ATR Signed-off-by: Luca Ceresoli --- drivers/i2c/i2c-mux.c | 218 +++++++++++++++++++++++++++- drivers/i2c/muxes/i2c-mux-pca954x.c | 2 +- include/linux/i2c-mux.h | 20 ++- 3 files changed, 233 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index f330690b4125..80c610cfe990 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -24,17 +24,131 @@ #include #include #include +#include #include #include +/** + * Hold the alias that as been assigned to a client. + */ +struct i2c_mux_cli2alias_pair { + struct list_head node; + struct i2c_client *client; + u16 alias; +}; + /* multiplexer per channel data */ struct i2c_mux_priv { struct i2c_adapter adap; struct i2c_algorithm algo; struct i2c_mux_core *muxc; u32 chan_id; + + /* ATR */ + struct list_head alias_list; + struct mutex atr_lock; /* Locks orig_addrs during ATR operation */ + u16 *orig_addrs; + unsigned orig_addrs_size; }; +static struct i2c_mux_cli2alias_pair * +i2c_mux_find_mapping_by_client(struct list_head *list, + struct i2c_client *client) +{ + struct i2c_mux_cli2alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client == client) + return c2a; + } + + return NULL; +} + +static struct i2c_mux_cli2alias_pair * +i2c_mux_find_mapping_by_addr(struct list_head *list, + u16 phys_addr) +{ + struct i2c_mux_cli2alias_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_mux_master_xfer() and + * similar. It must be followed by i2c_mux_unmap_msgs() to restore the + * original addresses. + */ +static int i2c_mux_map_msgs(struct i2c_mux_priv *priv, + struct i2c_msg msgs[], int num) + +{ + struct i2c_mux_core *muxc = priv->muxc; + static struct i2c_mux_cli2alias_pair *c2a; + int i; + + if (list_empty(&priv->alias_list)) + return 0; + + /* Ensure we have enough room to save the original addresses */ + if (unlikely(priv->orig_addrs_size < num)) { + void *new_buf = kmalloc(num * sizeof(priv->orig_addrs[0]), + GFP_KERNEL); + if (new_buf == NULL) { + dev_err(muxc->dev, + "Cannot allocate %d orig_addrs array", num); + return -ENOMEM; + } + + kfree(priv->orig_addrs); + priv->orig_addrs = new_buf; + priv->orig_addrs_size = num; + } + + for (i = 0; i < num; i++) { + priv->orig_addrs[i] = msgs[i].addr; + + c2a = i2c_mux_find_mapping_by_addr(&priv->alias_list, + msgs[i].addr); + if (c2a) { + msgs[i].addr = c2a->alias; + } else { + dev_warn(muxc->dev, "client 0x%02x not mapped!\n", + msgs[i].addr); + } + } + + return 0; +} + +/** + * Restore all message addres aliases with the original addresses. + * + * This function is internal for use in i2c_mux_master_xfer() and + * similar. + * + * @see i2c_mux_map_msgs() + */ +static void i2c_mux_unmap_msgs(struct i2c_mux_priv *priv, + struct i2c_msg msgs[], int num) +{ + int i; + + if (list_empty(&priv->alias_list)) + return; + + for (i = 0; i < num; i++) + msgs[i].addr = priv->orig_addrs[i]; +} + static int __i2c_mux_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { @@ -62,11 +176,31 @@ static int i2c_mux_master_xfer(struct i2c_adapter *adap, struct i2c_adapter *parent = muxc->parent; int ret; - /* Switch to the right mux port and perform the transfer. */ - + /* Switch to the right mux port */ ret = muxc->select(muxc, priv->chan_id); - if (ret >= 0) - ret = i2c_transfer(parent, msgs, num); + if (ret < 0) + goto out; + + /* Translate addresses if ATR enabled */ + if (muxc->atr) { + mutex_lock(&priv->atr_lock); + ret = i2c_mux_map_msgs(priv, msgs, num); + if (ret < 0) { + mutex_unlock(&priv->atr_lock); + goto out; + } + } + + /* Perform the transfer */ + ret = i2c_transfer(parent, msgs, num); + + /* Restore addresses if ATR enabled */ + if (muxc->atr) { + i2c_mux_unmap_msgs(priv, msgs, num); + mutex_unlock(&priv->atr_lock); + } + +out: if (muxc->deselect) muxc->deselect(muxc, priv->chan_id); @@ -235,11 +369,73 @@ struct i2c_adapter *i2c_root_adapter(struct device *dev) } EXPORT_SYMBOL_GPL(i2c_root_adapter); +static int i2c_mux_attach_client(struct i2c_adapter *adapter, + const struct i2c_board_info *info, + struct i2c_client *client) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + const struct i2c_mux_attach_operations *ops = muxc->attach_ops; + struct i2c_mux_cli2alias_pair *c2a; + u16 alias_id = 0; + int err = 0; + + if (ops && ops->i2c_mux_attach_client) + err = ops->i2c_mux_attach_client(muxc, priv->chan_id, + info, client, &alias_id); + if (err) + goto err_attach; + + if (alias_id != 0) { + c2a = kzalloc(sizeof(struct i2c_mux_cli2alias_pair), GFP_KERNEL); + if (!c2a) { + err = -ENOMEM; + goto err_alloc; + } + + c2a->client = client; + c2a->alias = alias_id; + list_add(&c2a->node, &priv->alias_list); + } + + return 0; + +err_alloc: + if (ops && ops->i2c_mux_detach_client) + ops->i2c_mux_detach_client(muxc, priv->chan_id, client); +err_attach: + return err; +} + +static void i2c_mux_detach_client(struct i2c_adapter *adapter, + struct i2c_client *client) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + const struct i2c_mux_attach_operations *ops = muxc->attach_ops; + struct i2c_mux_cli2alias_pair *c2a; + + if (ops && ops->i2c_mux_detach_client) + ops->i2c_mux_detach_client(muxc, priv->chan_id, client); + + c2a = i2c_mux_find_mapping_by_client(&priv->alias_list, client); + if (c2a != NULL) { + list_del(&c2a->node); + kfree(c2a); + } +} + +static const struct i2c_attach_operations i2c_mux_attach_operations = { + .attach_client = i2c_mux_attach_client, + .detach_client = i2c_mux_detach_client, +}; + struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent, struct device *dev, int max_adapters, int sizeof_priv, u32 flags, int (*select)(struct i2c_mux_core *, u32), - int (*deselect)(struct i2c_mux_core *, u32)) + int (*deselect)(struct i2c_mux_core *, u32), + const struct i2c_mux_attach_operations *attach_ops) { struct i2c_mux_core *muxc; @@ -259,8 +455,11 @@ struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent, muxc->arbitrator = true; if (flags & I2C_MUX_GATE) muxc->gate = true; + if (flags & I2C_MUX_ATR) + muxc->atr = true; muxc->select = select; muxc->deselect = deselect; + muxc->attach_ops = attach_ops; muxc->max_adapters = max_adapters; return muxc; @@ -300,6 +499,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, /* Set up private adapter data */ priv->muxc = muxc; priv->chan_id = chan_id; + INIT_LIST_HEAD(&priv->alias_list); + mutex_init(&priv->atr_lock); /* Need to do algo dynamically because we don't know ahead * of time what sort of physical adapter we'll be dealing with. @@ -328,6 +529,11 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, priv->adap.retries = parent->retries; priv->adap.timeout = parent->timeout; priv->adap.quirks = parent->quirks; + + if (muxc->attach_ops) { + priv->adap.attach_ops = &i2c_mux_attach_operations; + } + if (muxc->mux_locked) priv->adap.lock_ops = &i2c_mux_lock_ops; else @@ -426,6 +632,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; err_free_priv: + mutex_destroy(&priv->atr_lock); kfree(priv); return ret; } @@ -449,6 +656,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); i2c_del_adapter(adap); of_node_put(np); + mutex_destroy(&priv->atr_lock); kfree(priv); } } diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c index bfabf985e830..a777e765ce86 100644 --- a/drivers/i2c/muxes/i2c-mux-pca954x.c +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c @@ -362,7 +362,7 @@ static int pca954x_probe(struct i2c_client *client, return -ENODEV; muxc = i2c_mux_alloc(adap, dev, PCA954X_MAX_NCHANS, sizeof(*data), 0, - pca954x_select_chan, pca954x_deselect_mux); + pca954x_select_chan, pca954x_deselect_mux, NULL); if (!muxc) return -ENOMEM; data = i2c_mux_priv(muxc); diff --git a/include/linux/i2c-mux.h b/include/linux/i2c-mux.h index bd74d5706f3b..493eab3e8dbc 100644 --- a/include/linux/i2c-mux.h +++ b/include/linux/i2c-mux.h @@ -28,6 +28,20 @@ #ifdef __KERNEL__ #include +#include + +struct i2c_mux_core; + +struct i2c_mux_attach_operations { + int (*i2c_mux_attach_client)(struct i2c_mux_core *muxc, + u32 chan_id, + const struct i2c_board_info *info, + struct i2c_client *client, + u16 *alias_id); + void (*i2c_mux_detach_client)(struct i2c_mux_core *muxc, + u32 chan_id, + struct i2c_client *client); +}; struct i2c_mux_core { struct i2c_adapter *parent; @@ -35,11 +49,13 @@ struct i2c_mux_core { unsigned int mux_locked:1; unsigned int arbitrator:1; unsigned int gate:1; + unsigned int atr:1; void *priv; int (*select)(struct i2c_mux_core *, u32 chan_id); int (*deselect)(struct i2c_mux_core *, u32 chan_id); + const struct i2c_mux_attach_operations *attach_ops; int num_adapters; int max_adapters; @@ -50,12 +66,14 @@ struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent, struct device *dev, int max_adapters, int sizeof_priv, u32 flags, int (*select)(struct i2c_mux_core *, u32), - int (*deselect)(struct i2c_mux_core *, u32)); + int (*deselect)(struct i2c_mux_core *, u32), + const struct i2c_mux_attach_operations *attach_ops); /* flags for i2c_mux_alloc */ #define I2C_MUX_LOCKED BIT(0) #define I2C_MUX_ARBITRATOR BIT(1) #define I2C_MUX_GATE BIT(2) +#define I2C_MUX_ATR BIT(3) /* Address translator */ static inline void *i2c_mux_priv(struct i2c_mux_core *muxc) { From patchwork Tue Jan 8 22:39:52 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 10753075 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DBF036C5 for ; Tue, 8 Jan 2019 23:08:45 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C8BAF28D33 for ; Tue, 8 Jan 2019 23:08:45 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BBD6528E1E; Tue, 8 Jan 2019 23:08:45 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2FF3028D33 for ; Tue, 8 Jan 2019 23:08:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729467AbfAHXIk (ORCPT ); Tue, 8 Jan 2019 18:08:40 -0500 Received: from hostingweb31-40.netsons.net ([89.40.174.40]:49002 "EHLO hostingweb31-40.netsons.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729428AbfAHXIj (ORCPT ); Tue, 8 Jan 2019 18:08:39 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lucaceresoli.net; s=default; h=References:In-Reply-To:Message-Id:Date: Subject:Cc:To:From:Sender:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=zfYdVkidy/vlT1eq7QQpcaj0nAz1fir90pEsrfjtLi4=; b=q/1sY0umo0YjcjeEG14+7j7im AbVSU+vydgq3I/S0AR1gK+S9S2/iMjAa0N2GntVv5lQSABvXI1uA9qJ8s2n1qLd2Z9KEaFrqvi1DC XF8eFT1HJVv+cairipNKUM4hqKm4HclY7V7DmJaKXOPUTgS9pnl6qdQWrKCHRymXfsq9M=; Received: from [78.134.43.6] (port=50994 helo=melee.fritz.box) by hostingweb31.netsons.net with esmtpa (Exim 4.91) (envelope-from ) id 1gh02Z-00FWab-JF; Tue, 08 Jan 2019 23:40:07 +0100 From: Luca Ceresoli To: linux-media@vger.kernel.org Cc: Luca Ceresoli , Kieran Bingham , Laurent Pinchart , jacopo mondi , Vladimir Zapolskiy , Wolfram Sang , Peter Rosin , Mauro Carvalho Chehab , Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org Subject: [RFC 3/4] media: dt-bindings: add DS90UB954-Q1 video deserializer Date: Tue, 8 Jan 2019 23:39:52 +0100 Message-Id: <20190108223953.9969-4-luca@lucaceresoli.net> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190108223953.9969-1-luca@lucaceresoli.net> References: <20190108223953.9969-1-luca@lucaceresoli.net> X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - hostingweb31.netsons.net X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - lucaceresoli.net X-Get-Message-Sender-Via: hostingweb31.netsons.net: authenticated_id: luca+lucaceresoli.net/only user confirmed/virtual account not confirmed X-Authenticated-Sender: hostingweb31.netsons.net: luca@lucaceresoli.net X-Source: X-Source-Args: X-Source-Dir: Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This is a first, tentative DT layout to describe a 2-input video deserializer with I2C Address Translator and remote GPIOs. NOTES / TODO: * This GPIOs representation is not realistic, it has been used only to test that thing work. It shall be rewritten properly. Signed-off-by: Luca Ceresoli --- .../bindings/media/ti,ds90ub954-q1.txt | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/ti,ds90ub954-q1.txt diff --git a/Documentation/devicetree/bindings/media/ti,ds90ub954-q1.txt b/Documentation/devicetree/bindings/media/ti,ds90ub954-q1.txt new file mode 100644 index 000000000000..3024ef2df100 --- /dev/null +++ b/Documentation/devicetree/bindings/media/ti,ds90ub954-q1.txt @@ -0,0 +1,151 @@ +Texas Instruments DS90UB954-Q1 dual video Deserializer +====================================================== + +Required properties: + + - compatible: must be "ti,ds90ub954-q1" + - reg: I2C bus address of the chip (0x30..0xdd, based on strapping options) + - reset-gpios: chip reset GPIO, active low (connected to PDB pin of the chip) + - i2c-alias-pool: list of I2C addresses that are known to be available on the + "local" (SoC-to-deser) I2C bus; they will be picked at + runtime and used as aliases to reach remove I2C chips + + +Required subnodes: + - ports: A ports node with one port child node per device input and output + port, in accordance with the video interface bindings defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + port nodes are numbered as follows: + + Port Description + ----------------------------- + 0 Input from FPD-Link 3 RX port 0 + 1 Input from FPD-Link 3 RX port 1 + 2 CSI-2 output + - gpios: *** this is a temporary test implementation, ignore it + - i2c-mux: contains one child per RX port, each generates an I2C adapter + representing the I2C bus on the remote side + - rxports: contains one child per RX port, each describes one FPD-Link 3 port + with these fields: + - reg: the RX port index + - ser-i2c-alias: the alias to access the remore serializer from + the local bus + - bc-gpio-map: maps backchannel GPIO numbers to local GPIO inputs + with pairs + (TODO change when reimplementing the gpios subnode) + + +Device node example +------------------- + +&i2c0 { + deser@3d { + reg = <0x3d>; + compatible = "ti,ds90ub954-q1"; + reset-gpios = <&gpio1 2 0>; + + i2c-alias-pool = /bits/ 16 <0x20 0x21 0x22 0x23 0x24 0x25>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ds90ub954_fpd3_in0: endpoint { + remote-endpoint = <&remote_sensor_0_out>; + }; + }; + + // TODO enable both ports (and s/1/2/g in th MIPI port below) + // port@1 { + // reg = <1>; + // ds90ub954_fpd3_in1: endpoint { + // remote-endpoint = <&remote_sensor_1_out>; + // }; + // }; + + port@1 { + reg = <1>; + ds90ub954_mipi_out0: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&csirx_0_in>; + }; + }; + }; + + gpios { + #address-cells = <1>; + #size-cells = <0>; + + // From sensor to CPU + gpio@0 { + reg = <0>; + output; + source = <0>; // RX port 0 + function = <0>; + }; + + // CPU to sensor reset, active low + remote_sensor1_reset: gpio@1 { + reg = <1>; + input; + }; + }; + + i2c-mux { + #address-cells = <1>; + #size-cells = <0>; + + remote_i2c0: i2c@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = <400000>; + }; + + remote_i2c1: i2c@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = <400000>; + }; + }; + + rxports { + #address-cells = <1>; + #size-cells = <0>; + + rxport@0 { + reg = <0>; + ser-i2c-alias = <0x3e>; + + /* Map BC GPIO numbers to local GPIO inputs */ + bc-gpio-map = <1 &remote_sensor1_reset>; + }; + + rxport@1 { + reg = <1>; + ser-i2c-alias = <0x3f>; + }; + }; + }; +}; + +&remote_i2c0 { + remote_sensor0@1a { + reg = <0x1a>; + compatible = "sony,imx274"; + + #address-cells = <1>; + #size-cells = <0>; + reset-gpios = <&gpio1 4 0>; + + port@0 { + reg = <0>; + remote_sensor_0_out: endpoint { + remote-endpoint = <&ds90ub954_fpd3_in0>; + }; + }; + }; +}; From patchwork Tue Jan 8 22:39:53 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 10753077 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8391E1399 for ; Tue, 8 Jan 2019 23:08:50 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6B57B1FFF9 for ; Tue, 8 Jan 2019 23:08:50 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5B21A27E01; Tue, 8 Jan 2019 23:08:50 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.7 required=2.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 09CB71FFF9 for ; Tue, 8 Jan 2019 23:08:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729405AbfAHXIf (ORCPT ); Tue, 8 Jan 2019 18:08:35 -0500 Received: from hostingweb31-40.netsons.net ([89.40.174.40]:52968 "EHLO hostingweb31-40.netsons.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729312AbfAHXIf (ORCPT ); Tue, 8 Jan 2019 18:08:35 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lucaceresoli.net; s=default; h=References:In-Reply-To:Message-Id:Date: Subject:Cc:To:From:Sender:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=ZwbnxN6Fu6gDIDo1bdw0rvMjPVXjnbGoE3G7rzIimIE=; b=jIaNX/7p3lw5S4WqgjfTwnbkn bkTUKUFzrv8uOsq4r2k3qJ0JDhZz7tTpWT3tQ7LYC4wIimwLiYQUVh7r8re5JKbeQDxHERpd08QU6 +vHxJ8DQ9cnOAH93hKZKo/5n2H1fxRHTblH7eYCmJIesWvFpFj5gKHzenVZzVCXF20Y4I=; Received: from [78.134.43.6] (port=50994 helo=melee.fritz.box) by hostingweb31.netsons.net with esmtpa (Exim 4.91) (envelope-from ) id 1gh02a-00FWab-2e; Tue, 08 Jan 2019 23:40:08 +0100 From: Luca Ceresoli To: linux-media@vger.kernel.org Cc: Luca Ceresoli , Kieran Bingham , Laurent Pinchart , jacopo mondi , Vladimir Zapolskiy , Wolfram Sang , Peter Rosin , Mauro Carvalho Chehab , Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org Subject: [RFC 4/4] media: ds90ub954: new driver for TI DS90UB954-Q1 video deserializer Date: Tue, 8 Jan 2019 23:39:53 +0100 Message-Id: <20190108223953.9969-5-luca@lucaceresoli.net> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190108223953.9969-1-luca@lucaceresoli.net> References: <20190108223953.9969-1-luca@lucaceresoli.net> X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - hostingweb31.netsons.net X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - lucaceresoli.net X-Get-Message-Sender-Via: hostingweb31.netsons.net: authenticated_id: luca+lucaceresoli.net/only user confirmed/virtual account not confirmed X-Authenticated-Sender: hostingweb31.netsons.net: luca@lucaceresoli.net X-Source: X-Source-Args: X-Source-Dir: Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP LIMITATIONS / TODO: * Only I2C forwarding is quite complete. Most other features are only stubbed or not implemented at all. Remote GPIOs have a very naive implementation, where devicetree contains the values to write into registers. V4L2 callbacks are almost empty. * Testing is equally incomplete. * Only single camera and single flow is currently (partially) implemented * Interrupt handler not implemented: interrupts are polled in a kthread at the moment * The 'status' sysfs file is not sysfs compliant (contains multiple values). It was initially used to test the code and it should be rewritten differently. * In ds90_rxport_remove_serializer remove only 1 adapter, not all! (Needs work in i2c-mux) * The port registers are paged, we need to select the port each time and to use a lock to keep it selected. Make the driver simpler and more efficient by using a separate I2C address for each port (see datasheet, 7.5.1.2 Device Address). * Some registers are paged, thus regmap usage is probably wrong. 3 solutions: - use a different I2C address for each rxport (see above) - use a regmap per port and one for the common registers - don't use regmap * Instantiate the remote serializer under the mux adapter? See comment in ds90_rxport_add_serializer() Signed-off-by: Luca Ceresoli --- drivers/media/Kconfig | 1 + drivers/media/Makefile | 2 +- drivers/media/serdes/Kconfig | 13 + drivers/media/serdes/Makefile | 1 + drivers/media/serdes/ds90ub954.c | 1335 ++++++++++++++++++++++++++++++ 5 files changed, 1351 insertions(+), 1 deletion(-) create mode 100644 drivers/media/serdes/Kconfig create mode 100644 drivers/media/serdes/Makefile create mode 100644 drivers/media/serdes/ds90ub954.c diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 102eb35fcf3f..9ddb638973d6 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -251,5 +251,6 @@ source "drivers/media/i2c/Kconfig" source "drivers/media/spi/Kconfig" source "drivers/media/tuners/Kconfig" source "drivers/media/dvb-frontends/Kconfig" +source "drivers/media/serdes/Kconfig" endif # MEDIA_SUPPORT diff --git a/drivers/media/Makefile b/drivers/media/Makefile index 985d35ec6b29..09d5c6b5f0a6 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -10,7 +10,7 @@ media-objs := media-device.o media-devnode.o media-entity.o \ # I2C drivers should come before other drivers, otherwise they'll fail # when compiled as builtin drivers # -obj-y += i2c/ tuners/ +obj-y += i2c/ tuners/ serdes/ obj-$(CONFIG_DVB_CORE) += dvb-frontends/ # diff --git a/drivers/media/serdes/Kconfig b/drivers/media/serdes/Kconfig new file mode 100644 index 000000000000..6f8d02c187e6 --- /dev/null +++ b/drivers/media/serdes/Kconfig @@ -0,0 +1,13 @@ +menu "Video serializers and deserializers" + +config SERDES_DS90UB954 + tristate "TI DS90UB954-Q1 deserializer" + help + Device driver for the Texas Instruments "DS90UB954-Q1 Dual + 4.16 Gbps FPD-Link III Deserializer Hub With MIPI CSI-2 + Outputs for 2MP/60fps Cameras and RADAR". + + To compile this driver as a module, choose M here: the + module will be called ds90ub954. + +endmenu diff --git a/drivers/media/serdes/Makefile b/drivers/media/serdes/Makefile new file mode 100644 index 000000000000..19589a721cb4 --- /dev/null +++ b/drivers/media/serdes/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SERDES_DS90UB954) += ds90ub954.o diff --git a/drivers/media/serdes/ds90ub954.c b/drivers/media/serdes/ds90ub954.c new file mode 100644 index 000000000000..53fea6aa8e9a --- /dev/null +++ b/drivers/media/serdes/ds90ub954.c @@ -0,0 +1,1335 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Luca Ceresoli + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DS90_NUM_RXPORTS 2 /* Physical RX ports */ + +#define DS90_NUM_GPIOS 7 /* Physical GPIO pins */ +#define DS90_NUM_BC_GPIOS 4 /* Max GPIOs on Back Channel */ +#define DS90_GPIO_NSOURCES 8 /* Possible GPIO out sources */ +#define DS90_GPIO_NFUNCS 8 /* Possible GPIO out functions */ + +#define DS90_NUM_SLAVE_ALIASES 8 +#define DS90_MAX_POOL_ALIASES (DS90_NUM_RXPORTS * DS90_NUM_SLAVE_ALIASES) + +#define DS90_V4L2_NUM_PADS 2 /* TODO +1 for DS90_NUM_CSITXPORTS */ + +#define DS90_REG_I2C_DEV_ID 0x00 +#define DS90_REG_RESET 0x01 +#define DS90_REG_GEN_CONFIG 0x02 +#define DS90_REG_REV_MASK 0x03 +#define DS90_REG_DEVICE_STS 0x04 +#define DS90_REG_PAR_ERR_THOLD_HI 0x05 +#define DS90_REG_PAR_ERR_THOLD_LO 0x06 +#define DS90_REG_BCC_WDOG_CTL 0x07 +#define DS90_REG_I2C_CTL1 0x08 +#define DS90_REG_I2C_CTL2 0x09 +#define DS90_REG_SCL_HIGH_TIME 0x0A +#define DS90_REG_SCL_LOW_TIME 0x0B +#define DS90_REG_RX_PORT_CTL 0x0C +#define DS90_REG_IO_CTL 0x0D +#define DS90_REG_GPIO_PIN_STS 0x0E +#define DS90_REG_GPIO_INPUT_CTL 0x0F +#define DS90_REG_GPIO_PIN_CTL(n) (0x10 + (n)) /* n < DS90_NUM_GPIOS */ +#define DS90_REG_FS_CTL 0x18 +#define DS90_REG_FS_HIGH_TIME_1 0x19 +#define DS90_REG_FS_HIGH_TIME_0 0x1A +#define DS90_REG_FS_LOW_TIME_1 0x1B +#define DS90_REG_FS_LOW_TIME_0 0x1C +#define DS90_REG_MAX_FRM_HI 0x1D +#define DS90_REG_MAX_FRM_LO 0x1E +#define DS90_REG_CSI_PLL_CTL 0x1F +#define DS90_REG_FWD_CTL1 0x20 +#define DS90_REG_FWD_CTL2 0x21 +#define DS90_REG_FWD_STS 0x22 + +#define DS90_REG_INTERRUPT_CTL 0x23 +#define DS90_REG_INTERRUPT_CTL_INT_EN BIT(7) +#define DS90_REG_INTERRUPT_CTL_IE_CSI_TX0 BIT(4) +#define DS90_REG_INTERRUPT_CTL_IE_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define DS90_REG_INTERRUPT_STS 0x24 +#define DS90_REG_INTERRUPT_STS_INT BIT(7) +#define DS90_REG_INTERRUPT_STS_IS_CSI_TX0 BIT(4) +#define DS90_REG_INTERRUPT_STS_IS_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define DS90_REG_TS_CONFIG 0x25 +#define DS90_REG_TS_CONTROL 0x26 +#define DS90_REG_TS_LINE_HI 0x27 +#define DS90_REG_TS_LINE_LO 0x28 +#define DS90_REG_TS_STATUS 0x29 +#define DS90_REG_TIMESTAMP_P0_HI 0x2A +#define DS90_REG_TIMESTAMP_P0_LO 0x2B +#define DS90_REG_TIMESTAMP_P1_HI 0x2C +#define DS90_REG_TIMESTAMP_P1_LO 0x2D +#define DS90_REG_CSI_CTL 0x33 +#define DS90_REG_CSI_CTL2 0x34 +#define DS90_REG_CSI_STS 0x35 +#define DS90_REG_CSI_TX_ICR 0x36 +#define DS90_REG_CSI_TX_ISR 0x37 +#define DS90_REG_CSI_TX_ISR_IS_CSI_SYNC_ERROR BIT(3) +#define DS90_REG_CSI_TX_ISR_IS_CSI_PASS_ERROR BIT(1) + +#define DS90_REG_CSI_TEST_CTL 0x38 +#define DS90_REG_CSI_TEST_PATT_HI 0x39 +#define DS90_REG_CSI_TEST_PATT_LO 0x3A +#define DS90_REG_AEQ_CTL1 0x42 +#define DS90_REG_AEQ_ERR_THOLD 0x43 +#define DS90_REG_FPD3_CAP 0x4A +#define DS90_REG_RAW_EMBED_DTYPE 0x4B +#define DS90_REG_FPD3_PORT_SEL 0x4C + +#define DS90_REG_RX_PORT_STS1 0x4D +#define DS90_REG_RX_PORT_STS1_BCC_CRC_ERROR BIT(5) +#define DS90_REG_RX_PORT_STS1_LOCK_STS_CHG BIT(4) +#define DS90_REG_RX_PORT_STS1_BCC_SEQ_ERROR BIT(3) +#define DS90_REG_RX_PORT_STS1_PARITY_ERROR BIT(2) +#define DS90_REG_RX_PORT_STS1_PORT_PASS BIT(1) +#define DS90_REG_RX_PORT_STS1_LOCK_STS BIT(0) + +#define DS90_REG_RX_PORT_STS2 0x4E +#define DS90_REG_RX_PORT_STS2_LINE_LEN_UNSTABLE BIT(7) +#define DS90_REG_RX_PORT_STS2_LINE_LEN_CHG BIT(6) +#define DS90_REG_RX_PORT_STS2_FPD3_ENCODE_ERROR BIT(5) +#define DS90_REG_RX_PORT_STS2_BUFFER_ERROR BIT(4) +#define DS90_REG_RX_PORT_STS2_CSI_ERROR BIT(3) +#define DS90_REG_RX_PORT_STS2_FREQ_STABLE BIT(2) +#define DS90_REG_RX_PORT_STS2_CABLE_FAULT BIT(1) +#define DS90_REG_RX_PORT_STS2_LINE_CNT_CHG BIT(0) + +#define DS90_REG_RX_FREQ_HIGH 0x4F +#define DS90_REG_RX_FREQ_LOW 0x50 +#define DS90_REG_SENSOR_STS_0 0x51 +#define DS90_REG_SENSOR_STS_1 0x52 +#define DS90_REG_SENSOR_STS_2 0x53 +#define DS90_REG_SENSOR_STS_3 0x54 +#define DS90_REG_RX_PAR_ERR_HI 0x55 +#define DS90_REG_RX_PAR_ERR_LO 0x56 +#define DS90_REG_BIST_ERR_COUNT 0x57 + +#define DS90_REG_BCC_CONFIG 0x58 +#define DS90_REG_BCC_CONFIG_I2C_PASS_THROUGH BIT(6) + +#define DS90_REG_DATAPATH_CTL1 0x59 +#define DS90_REG_DATAPATH_CTL2 0x5A +#define DS90_REG_SER_ID 0x5B +#define DS90_REG_SER_ALIAS_ID 0x5C + +#define DS90_REG_SLAVE_ID(n) (0x5D + (n)) /* n < DS90_NUM_SLAVE_ALIASES */ +#define DS90_REG_SLAVE_ALIAS(n) (0x65 + (n)) /* n < DS90_NUM_SLAVE_ALIASES */ + +#define DS90_REG_PORT_CONFIG 0x6D +#define DS90_REG_BC_GPIO_CTL(n) (0x6E + (n)) /* n < 2 */ +#define DS90_REG_RAW10_ID 0x70 +#define DS90_REG_RAW12_ID 0x71 +#define DS90_REG_CSI_VC_MAP 0x72 +#define DS90_REG_LINE_COUNT_HI 0x73 +#define DS90_REG_LINE_COUNT_LO 0x74 +#define DS90_REG_LINE_LEN_1 0x75 +#define DS90_REG_LINE_LEN_0 0x76 +#define DS90_REG_FREQ_DET_CTL 0x77 +#define DS90_REG_MAILBOX_1 0x78 +#define DS90_REG_MAILBOX_2 0x79 + +#define DS90_REG_CSI_RX_STS 0x7A +#define DS90_REG_CSI_RX_STS_LENGTH_ERR BIT(3) +#define DS90_REG_CSI_RX_STS_CKSUM_ERR BIT(2) +#define DS90_REG_CSI_RX_STS_ECC2_ERR BIT(1) +#define DS90_REG_CSI_RX_STS_ECC1_ERR BIT(0) + +#define DS90_REG_CSI_ERR_COUNTER 0x7B +#define DS90_REG_PORT_CONFIG2 0x7C +#define DS90_REG_PORT_PASS_CTL 0x7D +#define DS90_REG_SEN_INT_RISE_CTL 0x7E +#define DS90_REG_SEN_INT_FALL_CTL 0x7F +#define DS90_REG_REFCLK_FREQ 0xA5 +#define DS90_REG_IND_ACC_CTL 0xB0 +#define DS90_REG_IND_ACC_ADDR 0xB1 +#define DS90_REG_IND_ACC_DATA 0xB2 +#define DS90_REG_BIST_CONTROL 0xB3 +#define DS90_REG_MODE_IDX_STS 0xB8 +#define DS90_REG_LINK_ERROR_COUNT 0xB9 +#define DS90_REG_FPD3_ENC_CTL 0xBA +#define DS90_REG_FV_MIN_TIME 0xBC +#define DS90_REG_GPIO_PD_CTL 0xBE +#define DS90_REG_PORT_DEBUG 0xD0 +#define DS90_REG_AEQ_CTL2 0xD2 +#define DS90_REG_AEQ_STATUS 0xD3 +#define DS90_REG_AEQ_BYPASS 0xD4 +#define DS90_REG_AEQ_MIN_MAX 0xD5 +#define DS90_REG_PORT_ICR_HI 0xD8 +#define DS90_REG_PORT_ICR_LO 0xD9 +#define DS90_REG_PORT_ISR_HI 0xDA +#define DS90_REG_PORT_ISR_LO 0xDB +#define DS90_REG_FC_GPIO_STS 0xDC +#define DS90_REG_FC_GPIO_ICR 0xDD +#define DS90_REG_SEN_INT_RISE_STS 0xDE +#define DS90_REG_SEN_INT_FALL_STS 0xDF +#define DS90_REG_FPD3_RX_ID0 0xF0 +#define DS90_REG_FPD3_RX_ID1 0xF1 +#define DS90_REG_FPD3_RX_ID2 0xF2 +#define DS90_REG_FPD3_RX_ID3 0xF3 +#define DS90_REG_FPD3_RX_ID4 0xF4 +#define DS90_REG_FPD3_RX_ID5 0xF5 +#define DS90_REG_I2C_RX0_ID 0xF8 +#define DS90_REG_I2C_RX1_ID 0xF9 + +struct ds90_rxport { + /* Errors and anomalies counters */ + u64 bcc_crc_error_count; + u64 bcc_seq_error_count; + u64 line_len_unstable_count; + u64 line_len_chg_count; + u64 fpd3_encode_error_count; + u64 buffer_error_count; + u64 line_cnt_chg_count; + u64 csi_rx_sts_length_err_count; + u64 csi_rx_sts_cksum_err_count; + u64 csi_rx_sts_ecc2_err_count; + u64 csi_rx_sts_ecc1_err_count; + + struct i2c_client *ser_client; + unsigned short ser_alias; /* ser i2c alias (lower 7 bits) */ + bool locked; + + struct ds90_data *ds90; /* Owner */ + unsigned short nport; /* Port number, and index in ds90->rxport[] */ +}; + +struct ds90_gpio { + bool output : 1; /* Direction */ + unsigned int source : 3; + unsigned int func : 3; +}; + +struct ds90_data { + struct i2c_client *client; + struct i2c_mux_core *muxc; /* i2c-mux with ATR */ + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct task_struct *kthread; + struct ds90_rxport *rxport[DS90_NUM_RXPORTS]; + struct ds90_gpio *gpio [DS90_NUM_GPIOS]; + + struct v4l2_subdev sd; + struct media_pad pads[DS90_V4L2_NUM_PADS]; + struct v4l2_mbus_framefmt fmt[DS90_V4L2_NUM_PADS]; + + struct mutex lock; /* Lock ATR table AND RX port selection */ + + /* Address Translator alias-to-slave map table */ + size_t atr_alias_num; /* Number of aliases configured */ + u16 atr_alias_id[DS90_MAX_POOL_ALIASES]; /* 0 = no alias */ + u16 atr_slave_id[DS90_MAX_POOL_ALIASES]; /* 0 = not in use */ +}; + +#define sd_to_ds90(_sd) container_of(_sd, struct ds90_data, sd) + +/* ----------------------------------------------------------------------------- + * Basic device access + */ + +static bool ds90_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg == DS90_REG_INTERRUPT_STS || + reg == DS90_REG_RX_PORT_STS1 || + reg == DS90_REG_RX_PORT_STS2 || + reg == DS90_REG_CSI_RX_STS || + reg == DS90_REG_CSI_TX_ISR); +} + +const struct regmap_config ds90_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = ds90_is_volatile_reg, +}; + +static int ds90_read(const struct ds90_data *ds90, + unsigned int reg, + unsigned int *val) +{ + int err; + + err = regmap_read(ds90->regmap, reg, val); + if (err) + dev_err(&ds90->client->dev, + "Cannot read register 0x%02x (%d)!\n", reg, err); + + return err; +} + +static int ds90_write(const struct ds90_data *ds90, + unsigned int reg, + unsigned int val) +{ + int err; + + err = regmap_write(ds90->regmap, reg, val); + if (err) + dev_err(&ds90->client->dev, + "Cannot write register 0x%02x (%d)!\n", reg, err); + + return err; +} + +static void ds90_reset(const struct ds90_data *ds90, bool keep_reset) +{ + gpiod_set_value_cansleep(ds90->reset_gpio, 0); + usleep_range(3000, 6000); /* min 2 ms */ + + if (!keep_reset) { + gpiod_set_value_cansleep(ds90->reset_gpio, 1); + usleep_range(2000, 4000); /* min 1 ms */ + } +} + +/* Select a port for register reading and writing */ +static int ds90_rxport_select(struct ds90_data *ds90, unsigned nport) +{ + int err = ds90_write(ds90, DS90_REG_FPD3_PORT_SEL, + (nport << 4) | BIT(nport)); + + return err; +} + +/* ----------------------------------------------------------------------------- + * CSI port + */ + +static void ds90_csi_handle_events(struct ds90_data *ds90) +{ + struct device *dev = &ds90->client->dev; + unsigned int csi_tx_isr; + int err; + + err = ds90_read(ds90, DS90_REG_CSI_TX_ISR, &csi_tx_isr); + + if (!err) { + if (csi_tx_isr & DS90_REG_CSI_TX_ISR_IS_CSI_SYNC_ERROR) + dev_warn(dev, "CSI_SYNC_ERROR\n"); + + if (csi_tx_isr & DS90_REG_CSI_TX_ISR_IS_CSI_PASS_ERROR) + dev_warn(dev, "CSI_PASS_ERROR\n"); + } +} + +/* ----------------------------------------------------------------------------- + * I2C-MUX with ATR (address translator) + */ + +static int ds90_mux_attach_client(struct i2c_mux_core *muxc, + u32 chan_id, + const struct i2c_board_info *info, + struct i2c_client *client, + u16 *alias_id) +{ + struct ds90_data **ds90p = i2c_mux_priv(muxc); + struct ds90_data *ds90 = *ds90p; + struct ds90_rxport *rxport = ds90->rxport[chan_id]; + struct device *dev = &ds90->client->dev; + u16 alias = 0; + int reg_idx; + int pool_idx; + int err = 0; + + mutex_lock(&ds90->lock); + + /* Find unused alias in table */ + + for (pool_idx = 0; pool_idx < ds90->atr_alias_num; pool_idx++) + if (ds90->atr_slave_id[pool_idx] == 0) + break; + + if (pool_idx == ds90->atr_alias_num) { + dev_warn(dev, "rx%d: alias pool exhausted\n", rxport->nport); + err = -EADDRNOTAVAIL; + goto out; + } + + alias = ds90->atr_alias_id[pool_idx]; + + /* Find first unused alias register */ + + ds90_rxport_select(ds90, rxport->nport); + + for (reg_idx = 0; reg_idx < DS90_NUM_SLAVE_ALIASES; reg_idx++) { + unsigned int regval; + + err = ds90_read(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), ®val); + if (!err && regval == 0) + break; + } + + if (reg_idx == DS90_NUM_SLAVE_ALIASES) { + dev_warn(dev, "rx%d: all aliases in use\n", rxport->nport); + err = -EADDRNOTAVAIL; + goto out; + } + + /* Map alias to slave */ + + ds90_write(ds90, DS90_REG_SLAVE_ID(reg_idx), client->addr << 1); + ds90_write(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), alias << 1); + + ds90->atr_slave_id[pool_idx] = client->addr; + + *alias_id = alias; /* tell the mux which alias we chose */ + + dev_info(dev, "rx%d: map alias 0x%02x to client 0x%02x\n", + rxport->nport, alias, client->addr); + +out: + mutex_unlock(&ds90->lock); + return err; +} + +static void ds90_mux_detach_client(struct i2c_mux_core *muxc, + u32 chan_id, + struct i2c_client *client) +{ + struct ds90_data **ds90p = i2c_mux_priv(muxc); + struct ds90_data *ds90 = *ds90p; + struct ds90_rxport *rxport = ds90->rxport[chan_id]; + struct device *dev = &ds90->client->dev; + u16 alias = 0; + int reg_idx; + int pool_idx; + + mutex_lock(&ds90->lock); + + /* Find alias mapped to this client */ + + for (pool_idx = 0; pool_idx < ds90->atr_alias_num; pool_idx++) + if (ds90->atr_slave_id[pool_idx] == client->addr) + break; + + if (pool_idx == ds90->atr_alias_num) { + dev_err(dev, "rx%d: client 0x%02x is not mapped!\n", + rxport->nport, client->addr); + goto out; + } + + alias = ds90->atr_alias_id[pool_idx]; + + /* Find alias register used for this client */ + + ds90_rxport_select(ds90, rxport->nport); + + for (reg_idx = 0; reg_idx < DS90_NUM_SLAVE_ALIASES; reg_idx++) { + unsigned int regval; + int err; + + err = ds90_read(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), ®val); + if (!err && regval == (alias << 1)) + break; + } + + if (reg_idx == DS90_NUM_SLAVE_ALIASES) { + dev_err(dev, "rx%d: cannot find alias 0x%02x reg (client 0x%02x)!\n", + rxport->nport, alias, client->addr); + goto out; + } + + /* Unmap */ + + ds90_write(ds90, DS90_REG_SLAVE_ALIAS(reg_idx), 0); + ds90->atr_slave_id[pool_idx] = 0; + + dev_info(dev, "rx%d: unmapped alias 0x%02x from client 0x%02x\n", + rxport->nport, alias, client->addr); + +out: + mutex_unlock(&ds90->lock); +} + +static const struct i2c_mux_attach_operations ds90_mux_attach_ops = { + .i2c_mux_attach_client = ds90_mux_attach_client, + .i2c_mux_detach_client = ds90_mux_detach_client, +}; + +static int ds90_mux_select(struct i2c_mux_core *muxc, u32 chan_id) +{ + struct ds90_data **ds90p = i2c_mux_priv(muxc); + struct ds90_data *ds90 = *ds90p; + + return ds90_rxport_select(ds90, chan_id); +} + +/* ----------------------------------------------------------------------------- + * RX ports + */ + +static ssize_t locked_show(struct device *dev, + struct device_attribute *attr, + char *buf); +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, + char *buf); + +static struct device_attribute dev_attr_locked[] = { + __ATTR_RO(locked), + __ATTR_RO(locked), +}; + +static struct device_attribute dev_attr_status[] = { + __ATTR_RO(status), + __ATTR_RO(status), +}; + +static struct attribute *ds90_rxport0_attrs[] = { + &dev_attr_locked[0].attr, + &dev_attr_status[0].attr, + NULL +}; + +static struct attribute *ds90_rxport1_attrs[] = { + &dev_attr_locked[1].attr, + &dev_attr_status[1].attr, + NULL +}; + +static ssize_t locked_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int nport = (attr - dev_attr_locked); + const struct ds90_data *ds90 = dev_get_drvdata(dev); + const struct ds90_rxport *rxport = ds90->rxport[nport]; + + return scnprintf(buf, PAGE_SIZE, "%d", rxport->locked); +} + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int nport = (attr - dev_attr_status); + const struct ds90_data *ds90 = dev_get_drvdata(dev); + const struct ds90_rxport *rxport = ds90->rxport[nport]; + + return scnprintf(buf, PAGE_SIZE, + "bcc_crc_error_count = %llu\n" + "bcc_seq_error_count = %llu\n" + "line_len_unstable_count = %llu\n" + "line_len_chg_count = %llu\n" + "fpd3_encode_error_count = %llu\n" + "buffer_error_count = %llu\n" + "line_cnt_chg_count = %llu\n" + "csi_rx_sts_length_err_count = %llu\n" + "csi_rx_sts_cksum_err_count = %llu\n" + "csi_rx_sts_ecc2_err_count = %llu\n" + "csi_rx_sts_ecc1_err_count = %llu\n", + rxport->bcc_crc_error_count, + rxport->bcc_seq_error_count, + rxport->line_len_unstable_count, + rxport->line_len_chg_count, + rxport->fpd3_encode_error_count, + rxport->buffer_error_count, + rxport->line_cnt_chg_count, + rxport->csi_rx_sts_length_err_count, + rxport->csi_rx_sts_cksum_err_count, + rxport->csi_rx_sts_ecc2_err_count, + rxport->csi_rx_sts_ecc1_err_count); +} + +struct attribute_group ds90_rxport_attr_group[] = { + { .name = "rx0", .attrs = ds90_rxport0_attrs }, + { .name = "rx1", .attrs = ds90_rxport1_attrs }, +}; + +/* + * Instantiate serializer and i2c adapter for the just-locked remote + * end. + * + * @note Must be called with ds90->lock not held! The added i2c + * adapter will probe new slaves, which can request i2c transfers, + * ending up in calling ds90_mux_attach_client() where the lock is + * taken. + */ +static int ds90_rxport_add_serializer(struct ds90_data *ds90, int nport) +{ + struct ds90_rxport *rxport = ds90->rxport[nport]; + struct device *dev = &ds90->client->dev; + struct i2c_board_info ser_info = { .type = "ds90ub953-q1" }; + struct i2c_client *client; + int err; + + /* + * Adding the serializer under the rxport-specific adapter + * would be cleaner, but it would need tweaks to bypass the + * alias table. Adding to the upstream adapter is way simpler. + */ + ser_info.addr = rxport->ser_alias; + client = i2c_new_device(ds90->client->adapter, &ser_info); + if (!client) { + dev_err(dev, "rx%d: cannot add %s i2c device", + nport, ser_info.type); + return -EIO; + } + rxport->ser_client = client; + + err = i2c_mux_add_adapter(ds90->muxc, 0, nport, 0); + if (err) { + dev_err(dev, "rx%d: cannot add adapter", nport); + i2c_unregister_device(rxport->ser_client); + rxport->ser_client = NULL; + } + + return err; +} + +static void ds90_rxport_remove_serializer(struct ds90_data *ds90, int nport) +{ + struct ds90_rxport *rxport = ds90->rxport[nport]; + + i2c_mux_del_adapters(ds90->muxc); /* FIXME remove one only */ + + if (rxport->ser_client) { + i2c_unregister_device(rxport->ser_client); + rxport->ser_client = NULL; + } +} + +/* + * Map a local GPIO output to a back-channel GPIO number. + * + * Example: GPIO from SoC -> GPIO input at deser -> GPIO on FPD-3 link. + */ +static int ds90_rxport_map_bc_gpios(struct ds90_data *ds90, + int nport, + const struct device_node *np) +{ + struct device *dev = &ds90->client->dev; + const __be32 *bc_gpio_map; + int bc_gpio_map_len; + struct device_node *local_gpio_np; + const __be32 *local_gpio_regp; + u32 bc_gpio_idx; + u32 local_gpio_ph; + u32 local_gpio_reg; + unsigned int reg; + unsigned reg_shift; + int err; + int i; + + bc_gpio_map = of_get_property(np, "bc-gpio-map", &bc_gpio_map_len); + if (!bc_gpio_map) { + dev_dbg(dev, "rx%d: bc-gpio-map NOT FOUND \n", nport); + return -ENOENT; + } + + bc_gpio_map_len /= sizeof(*bc_gpio_map); + + for (i = 0; i + 2 <= bc_gpio_map_len; i += 2) { + bc_gpio_idx = be32_to_cpu(bc_gpio_map[i]); + local_gpio_ph = be32_to_cpu(bc_gpio_map[i + 1]); + + if (bc_gpio_idx >= DS90_NUM_BC_GPIOS) + continue; + + local_gpio_np = of_find_node_by_phandle(local_gpio_ph); + if (!local_gpio_np) + continue; + + local_gpio_regp = of_get_property(local_gpio_np, "reg", NULL); + if (!local_gpio_regp) + goto put_and_continue; + + local_gpio_reg = be32_to_cpup(local_gpio_regp); + if (local_gpio_reg >= DS90_NUM_GPIOS) + goto put_and_continue; + + reg = DS90_REG_BC_GPIO_CTL(bc_gpio_idx / 2); + reg_shift = (bc_gpio_idx % 2) * 4; + + dev_dbg(dev, "rx%d: BC GPIO %d from local GPIO %d\n", + nport, bc_gpio_idx, local_gpio_reg); + + err = regmap_update_bits(ds90->regmap, reg, 0xf << reg_shift, + local_gpio_reg << reg_shift); + if (err) + dev_err(dev, "rx%d: Cannot update reg 0x%02x (%d)\n", + nport, reg, err); + + put_and_continue: + of_node_put(local_gpio_np); + } + + return 0; +} + +static int ds90_rxport_probe_one(struct ds90_data *ds90, + const struct device_node *np) +{ + struct device *dev = &ds90->client->dev; + struct ds90_rxport *rxport; + u32 ser_alias; + u32 nport; + int err; + + if (of_property_read_u32(np, "reg", &nport) != 0 || + nport >= DS90_NUM_RXPORTS) + return -EINVAL; + + if (ds90->rxport[nport]) { + dev_err(dev, "OF: %s: reg value %d is duplicated\n", + of_node_full_name(np), nport); + return -EADDRINUSE; + } + + if (of_property_read_u32(np, "ser-i2c-alias", &ser_alias) != 0 || + ser_alias == 0) { + dev_err(dev, "OF: %s: invalid ser-i2c-alias\n", + of_node_full_name(np)); + return -EINVAL; + } + + rxport = devm_kzalloc(dev, sizeof(*rxport), GFP_KERNEL); + if (!rxport) + return -ENOMEM; + + rxport->nport = nport; + rxport->ser_alias = ser_alias; + rxport->ds90 = ds90; + + dev_dbg(dev, "OF: rxport[%d], alias=0x%02x\n", nport, ser_alias); + + ds90->rxport[nport] = rxport; /* Needed by successive calls */ + + ds90_rxport_select(ds90, nport); + + ds90_rxport_map_bc_gpios(ds90, nport, np); + + regmap_update_bits(ds90->regmap, DS90_REG_INTERRUPT_CTL, + DS90_REG_INTERRUPT_CTL_IE_RX(nport), ~0); + + /* Enable all interrupt sources from this port */ + ds90_write(ds90, DS90_REG_PORT_ICR_HI, 0x07); + ds90_write(ds90, DS90_REG_PORT_ICR_LO, 0x7f); + + regmap_update_bits(ds90->regmap, + DS90_REG_BCC_CONFIG, + DS90_REG_BCC_CONFIG_I2C_PASS_THROUGH, ~0); + + /* Enable I2C communication to the serializer via the alias addr */ + ds90_write(ds90, DS90_REG_SER_ALIAS_ID, rxport->ser_alias << 1); + + err = sysfs_create_group(&dev->kobj, &ds90_rxport_attr_group[nport]); + if (err) { + dev_err(dev, "rx%d: failed creating sysfs group", nport); + goto err_sysfs; + } + + return 0; + +err_sysfs: + ds90->rxport[nport] = NULL; + return err; +} + +static void ds90_rxport_remove_one(struct ds90_data *ds90, int nport) +{ + struct device *dev = &ds90->client->dev; + + ds90_rxport_remove_serializer(ds90, nport); + sysfs_remove_group(&dev->kobj, &ds90_rxport_attr_group[nport]); +} + +static int ds90_rxport_probe(struct ds90_data *ds90) +{ + struct device_node *ds90_node = ds90->client->dev.of_node; + struct i2c_adapter *parent_adap = ds90->client->adapter; + struct device *dev = &ds90->client->dev; + const struct device_node *rxports_node; + struct device_node *rxport_node; + int err = 0; + int i; + + /* + * TODO Maybe could be simplified by allocating the muxc in + * ds90_probe() with the ds90 as its "priv" data? + * Code: i2c_mux_alloc(parent_adap, DS90_NUM_RXPORTS, sizeof(ds90_data), ...) + * ds90_data *ds90 = i2c_mux_priv(muxc) + */ + struct ds90_data **mux_data; + ds90->muxc = i2c_mux_alloc(parent_adap, dev, DS90_NUM_RXPORTS, + sizeof(*mux_data), + I2C_MUX_LOCKED | I2C_MUX_ATR, + ds90_mux_select, NULL, + &ds90_mux_attach_ops); + if (!ds90->muxc) + return -ENOMEM; + mux_data = i2c_mux_priv(ds90->muxc); + *mux_data = ds90; + + rxports_node = of_get_child_by_name(ds90_node, "rxports"); + if (!rxports_node) { + dev_warn(dev, "OF: no rxports defined!\n"); + } else { + for_each_child_of_node(rxports_node, rxport_node) { + err = ds90_rxport_probe_one(ds90, rxport_node); + if (err) + break; + } + of_node_put(rxport_node); + } + + if (err) + for (i = 0; i < DS90_NUM_RXPORTS; i++) + if (ds90->rxport[i]) + ds90_rxport_remove_one(ds90, i); + + return err; +} + +static void ds90_rxport_remove(struct ds90_data *ds90) +{ + int i; + + i2c_mux_del_adapters(ds90->muxc); + + for (i = 0; i < DS90_NUM_RXPORTS; i++) + if (ds90->rxport[i]) + ds90_rxport_remove_one(ds90, i); +} + +static void ds90_rxport_handle_events(struct ds90_data *ds90, int nport) +{ + struct ds90_rxport *rxport = ds90->rxport[nport]; + struct device *dev = &ds90->client->dev; + unsigned int rx_port_sts1; + unsigned int rx_port_sts2; + unsigned int csi_rx_sts; + bool locked; + int err; + + mutex_lock(&ds90->lock); + + /* Select port for register reading and writing */ + err = ds90_rxport_select(ds90, nport); + + /* Read interrupts (also clears most of them) */ + if (!err) + err = ds90_read(ds90, DS90_REG_RX_PORT_STS1, &rx_port_sts1); + if (!err) + err = ds90_read(ds90, DS90_REG_RX_PORT_STS2, &rx_port_sts2); + if (!err) + err = ds90_read(ds90, DS90_REG_CSI_RX_STS, &csi_rx_sts); + + mutex_unlock(&ds90->lock); + + if (err) + return; + + if (rx_port_sts1 & DS90_REG_RX_PORT_STS1_BCC_CRC_ERROR) + rxport->bcc_crc_error_count++; + + if (rx_port_sts1 & DS90_REG_RX_PORT_STS1_BCC_SEQ_ERROR) + rxport->bcc_seq_error_count++; + + if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_LINE_LEN_UNSTABLE) + rxport->line_len_unstable_count++; + + if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_LINE_LEN_CHG) + rxport->line_len_chg_count++; + + if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_FPD3_ENCODE_ERROR) + rxport->fpd3_encode_error_count++; + + if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_BUFFER_ERROR) + rxport->buffer_error_count++; + + if (rx_port_sts2 & DS90_REG_RX_PORT_STS2_LINE_CNT_CHG) + rxport->line_cnt_chg_count++; + + if (csi_rx_sts & DS90_REG_CSI_RX_STS_LENGTH_ERR) + rxport->csi_rx_sts_length_err_count++; + + if (csi_rx_sts & DS90_REG_CSI_RX_STS_CKSUM_ERR) + rxport->csi_rx_sts_cksum_err_count++; + + if (csi_rx_sts & DS90_REG_CSI_RX_STS_ECC2_ERR) + rxport->csi_rx_sts_ecc2_err_count++; + + if (csi_rx_sts & DS90_REG_CSI_RX_STS_ECC1_ERR) + rxport->csi_rx_sts_ecc1_err_count++; + + /* Update locked status */ + locked = rx_port_sts1 & DS90_REG_RX_PORT_STS1_LOCK_STS; + if (locked && !rxport->locked) { + dev_info(dev, "rx%d LOCKED\n", nport); + /* See note about locking in ds90_rxport_add_serializer()! */ + ds90_rxport_add_serializer(ds90, nport); + } + else if (!locked && rxport->locked) { + dev_info(dev, "rx%d NOT LOCKED\n", nport); + ds90_rxport_remove_serializer(ds90, nport); + } + rxport->locked = locked; +} + +/* ----------------------------------------------------------------------------- + * GPIOs + */ + +/* TODO struct ds90_gpio is not used outside of this function. Remove it??? */ +static int ds90_gpio_probe_one(struct ds90_data *ds90, struct device_node *np) +{ + struct device *dev = &ds90->client->dev; + struct ds90_gpio *gpio; + u32 reg; + u32 source; + u32 func; + + if (of_property_read_u32(np, "reg", ®) != 0 || + reg >= DS90_NUM_GPIOS) { + dev_err(dev, "OF: %s: invalid reg\n", of_node_full_name(np)); + return -EINVAL; + } + + if (ds90->gpio[reg]) { + dev_err(dev, "OF: %s: reg value %d is duplicated\n", + of_node_full_name(np), reg); + return -EADDRINUSE; + } + + gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + gpio->output = false; /* A safe default */ + + if (of_property_read_bool(np, "output")) { + if (of_property_read_u32(np, "source", &source) == 0 && + of_property_read_u32(np, "function", &func) == 0 && + source < DS90_GPIO_NSOURCES && + func < DS90_GPIO_NFUNCS) { + gpio->output = true; + gpio->source = source; + gpio->func = func; + } else { + dev_err(dev, + "OF: %s: incorrect output parameters, fallback to input!\n", + of_node_full_name(np)); + } + } else if (!of_property_read_bool(np, "input")) { + dev_err(dev, + "OF: %s: no direction specified, fallback to input!\n", + of_node_full_name(np)); + } + + if (gpio->output) + dev_dbg(dev, "OF: gpio[%d], output, source %d, function %d\n", + reg, gpio->source, gpio->func); + else + dev_dbg(dev, "OF: gpio[%d], input\n", reg); + + ds90->gpio[reg] = gpio; + + if (gpio->output) { + u8 pin_ctl = gpio->source << 5 | gpio->func << 2 | 1; + + regmap_update_bits(ds90->regmap, DS90_REG_GPIO_INPUT_CTL, + BIT(reg), 0); + ds90_write(ds90, DS90_REG_GPIO_PIN_CTL(reg), pin_ctl); + } else { + regmap_update_bits(ds90->regmap, DS90_REG_GPIO_INPUT_CTL, + BIT(reg), ~0); + } + + return 0; +} + +static int ds90_gpio_probe(struct ds90_data *ds90) +{ + struct device_node *ds90_node = ds90->client->dev.of_node; + struct device *dev = &ds90->client->dev; + const struct device_node *gpios_node; + struct device_node *gpio_node; + int err = 0; + + gpios_node = of_get_child_by_name(ds90_node, "gpios"); + if (!gpios_node) { + dev_warn(dev, "OF: no gpios defined!\n"); + } else { + for_each_child_of_node(gpios_node, gpio_node) { + err = ds90_gpio_probe_one(ds90, gpio_node); + if (err) + break; + } + of_node_put(gpio_node); + } + + return err; +} + +/* ----------------------------------------------------------------------------- + * V4L2 + */ + +static int ds90_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ds90_data *ds90 = sd_to_ds90(sd); + struct device *dev = &ds90->client->dev; + + dev_info(dev, "%s: TODO\n", __func__); + + return 0; +} + +static int ds90_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ds90_data *ds90 = sd_to_ds90(sd); + struct device *dev = &ds90->client->dev; + + dev_info(dev, "%s: TODO\n", __func__); + + return 0; +} + +static struct v4l2_mbus_framefmt * +ds90_get_pad_format(struct ds90_data *ds90, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&ds90->sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &ds90->fmt[pad]; + default: + return NULL; + } +} + +static int ds90_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ds90_data *ds90 = sd_to_ds90(sd); + struct device *dev = &ds90->client->dev; + + dev_info(dev, "%s: TODO\n", __func__); + + return 0; +} + +static int ds90_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ds90_data *ds90 = sd_to_ds90(sd); + struct v4l2_mbus_framefmt *cfg_fmt; + struct device *dev = &ds90->client->dev; + + dev_dbg(dev, "pad %d which %d", format->pad, format->which); + + if (format->pad >= DS90_V4L2_NUM_PADS) + return -EINVAL; + + cfg_fmt = ds90_get_pad_format(ds90, cfg, format->pad, format->which); + if (!cfg_fmt) + return -EINVAL; + + format->format = *cfg_fmt; + + return 0; +} + +static int ds90_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ds90_data *ds90 = sd_to_ds90(sd); + struct device *dev = &ds90->client->dev; + + dev_info(dev, "%s: TODO\n", __func__); + + return 0; +} + +static const struct v4l2_subdev_video_ops ds90_video_ops = { + .s_stream = ds90_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ds90_pad_ops = { + .enum_mbus_code = ds90_enum_mbus_code, + .get_fmt = ds90_get_fmt, + .set_fmt = ds90_set_fmt, + .get_frame_desc = ds90_get_frame_desc, +}; + +static const struct v4l2_subdev_ops ds90_subdev_ops = { + .video = &ds90_video_ops, + .pad = &ds90_pad_ops, +}; + +static void ds90_init_format(struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = 1920; + fmt->height = 1080; + fmt->code = MEDIA_BUS_FMT_SRGGB8_1X8; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; +} + +static int ds90_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format; + unsigned int i; + + for (i = 0; i < DS90_V4L2_NUM_PADS; i++) { + format = v4l2_subdev_get_try_format(subdev, fh->pad, i); + ds90_init_format(format); + } + + return 0; +} + +static const struct v4l2_subdev_internal_ops ds90_subdev_internal_ops = { + .open = ds90_open, +}; + +/* ----------------------------------------------------------------------------- + * Core + */ + +static int ds90_run(void *arg) +{ + struct ds90_data *ds90 = arg; + unsigned int int_sts; + int err; + int i; + + while (1) { + if (kthread_should_stop()) + break; + + err = ds90_read(ds90, DS90_REG_INTERRUPT_STS, &int_sts); + + if (!err && int_sts) { + if (int_sts & DS90_REG_INTERRUPT_STS_IS_CSI_TX0) + ds90_csi_handle_events(ds90); + + for (i = 0; i < DS90_NUM_RXPORTS; i++) + if (int_sts & DS90_REG_INTERRUPT_STS_IS_RX(i) && + ds90->rxport[i]) + ds90_rxport_handle_events(ds90, i); + } + + msleep(1000); + } + + return 0; +} + +static int ds90_parse_dt(struct ds90_data *ds90) +{ + struct device_node *np = ds90->client->dev.of_node; + struct device *dev = &ds90->client->dev; + int n; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + n = of_property_read_variable_u16_array(np, "i2c-alias-pool", + ds90->atr_alias_id, + 2, DS90_MAX_POOL_ALIASES); + if (n < 0) + dev_warn(dev, + "OF: no i2c-alias-pool, can't access remote I2C slaves"); + + ds90->atr_alias_num = n; + + dev_dbg(dev, "i2c-alias-pool has %zu aliases", ds90->atr_alias_num); + + return 0; +} + +static int ds90_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ds90_data *ds90; + unsigned int rev_mask; + int err; + int i; + + ds90 = devm_kzalloc(&client->dev, sizeof(*ds90), GFP_KERNEL); + if (!ds90) + return -ENOMEM; + + ds90->client = client; + + err = ds90_parse_dt(ds90); + if (err) + goto err_parse_dt; + + /* get reset pin from DT */ + ds90->reset_gpio = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ds90->reset_gpio)) { + if (PTR_ERR(ds90->reset_gpio) != -EPROBE_DEFER) + dev_err(&client->dev, "Cannot get reset GPIO"); + return PTR_ERR(ds90->reset_gpio); + } + + mutex_init(&ds90->lock); + + i2c_set_clientdata(client, ds90); + + /* initialize regmap */ + ds90->regmap = devm_regmap_init_i2c(client, &ds90_regmap_config); + if (IS_ERR(ds90->regmap)) { + err = PTR_ERR(ds90->regmap); + dev_err(&client->dev, "regmap init failed (%d)\n", err); + goto err_regmap; + } + + /* Runtime check register accessibility */ + ds90_reset(ds90, false); + + err = ds90_read(ds90, DS90_REG_REV_MASK, &rev_mask); + if (err) { + dev_err(&client->dev, + "Cannot read first register (%d), abort\n", err); + goto err_reg_read; + } + + /* Init rxports, remote GPIOs and I2C */ + + err = ds90_gpio_probe(ds90); + if (err) { + dev_err(&client->dev, "Error probing gpios (%d)\n", err); + goto err_configure_deser_gpios; + } + + err = ds90_rxport_probe(ds90); + if (err) + goto err_rxport_probe; + + /* V4L2 */ + + for (i = 0; i < DS90_V4L2_NUM_PADS; i++) + ds90_init_format(&ds90->fmt[i]); + + v4l2_i2c_subdev_init(&ds90->sd, client, &ds90_subdev_ops); + + /* Let both the I2C client and the subdev point to us */ + i2c_set_clientdata(client, ds90); /* v4l2_i2c_subdev_init writes it */ + v4l2_set_subdevdata(&ds90->sd, ds90); + + ds90->sd.internal_ops = &ds90_subdev_internal_ops; + ds90->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + /* TODO MEDIA_ENT_F_VID_IF_BRIDGE (since kernel 4.13) is better? */ + ds90->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + + ds90->pads[0].flags = MEDIA_PAD_FL_SINK; + ds90->pads[1].flags = MEDIA_PAD_FL_SOURCE; + err = media_entity_pads_init(&ds90->sd.entity, + DS90_V4L2_NUM_PADS, ds90->pads); + if (err) + goto err_pads_init; + + err = v4l2_async_register_subdev(&ds90->sd); + if (err) { + dev_err(&client->dev, "v4l2_async_register_subdev error %d\n", err); + goto err_register_subdev; + } + + /* Kick off */ + + regmap_update_bits(ds90->regmap, + DS90_REG_INTERRUPT_CTL, + DS90_REG_INTERRUPT_CTL_INT_EN, ~0); + + ds90->kthread = kthread_run(ds90_run, ds90, dev_name(&client->dev)); + if (IS_ERR(ds90->kthread)) { + err = PTR_ERR(ds90->kthread); + dev_err(&client->dev, "Cannot create kthread (%d)\n", err); + goto err_kthread; + } + + dev_info(&client->dev, "Successfully probed (rev/mask %02x)\n", rev_mask); + + return 0; + +err_kthread: + v4l2_async_unregister_subdev(&ds90->sd); +err_register_subdev: + media_entity_cleanup(&ds90->sd.entity); +err_pads_init: + ds90_rxport_remove(ds90); +err_rxport_probe: +err_configure_deser_gpios: +err_reg_read: + ds90_reset(ds90, true); +err_regmap: + mutex_destroy(&ds90->lock); +err_parse_dt: + return err; +} + +static int ds90_remove(struct i2c_client *client) +{ + struct ds90_data *ds90 = i2c_get_clientdata(client); + + dev_info(&client->dev, "Removing\n"); + + kthread_stop(ds90->kthread); + v4l2_async_unregister_subdev(&ds90->sd); + media_entity_cleanup(&ds90->sd.entity); + ds90_rxport_remove(ds90); + ds90_reset(ds90, true); + mutex_destroy(&ds90->lock); + + return 0; +} + +static const struct i2c_device_id ds90_id[] = { + { "ds90ub954-q1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ds90_id); + +#ifdef CONFIG_OF +static const struct of_device_id ds90_dt_ids[] = { + { .compatible = "ti,ds90ub954-q1", }, + { } +}; +MODULE_DEVICE_TABLE(of, ds90_dt_ids); +#endif + +static struct i2c_driver ds90ub954_driver = { + .probe = ds90_probe, + .remove = ds90_remove, + .id_table = ds90_id, + .driver = { + .name = "ds90ub954", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ds90_dt_ids), + }, +}; + +module_i2c_driver(ds90ub954_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB954-Q1 CSI-2 dual deserializer driver"); +MODULE_AUTHOR("Luca Ceresoli ");