From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-lf1-f53.google.com (mail-lf1-f53.google.com [209.85.167.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 137D7324B35 for ; Mon, 17 Nov 2025 09:05:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.53 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763370342; cv=none; b=QJpsGTfVR8t4wwQBUOzoXlgKpVNQibt1IRzNUP2NjKa9uLC28QpjEmksYiWKAkP4h80NnMa1w7fRVKnmHUAWHWHC3F7qpldSw7wg2YQmQEiuz8lqHPpns4cyjYOnJBLmTRFGOj9iI54rwtHMZRCUGq5rKIAu17NZD8FRZNVdscA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763370342; c=relaxed/simple; bh=o4Zt8X+zyI6wUYVNms/IgvHSL1cE9RhNZwSvJA7JohU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=naoS51BWHKDXhCktGXxyA4cie279Yt/gIRpdVesHLEaMYb7+mOjfRkwB74MQUEI889sQsOGl0A4cU91ipuL5ayhytyQz2vJxCaPgFYvUgogNoHewZGdejcaegtwU1/ntcOmNBJeygfF6gnpaxqIgwsgrRa55dk69nHMRxQz/ciE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=lNQRwmeH; arc=none smtp.client-ip=209.85.167.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lNQRwmeH" Received: by mail-lf1-f53.google.com with SMTP id 2adb3069b0e04-5957753e0efso4314156e87.1 for ; Mon, 17 Nov 2025 01:05:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1763370338; x=1763975138; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=4Isv7N56R7GLLyxOJRI4a7jcx/LSO3hI5hjNg+m1oWY=; b=lNQRwmeHoBS001gEXYZjAMn9ept6SgFLXyMf8NQTvr20ZqXbBnFEOuWC6jxOQ1f6aM eZ51ujJRILZkPacpoMPReyy7LpVZeOd1xRwqHOPmVwNOqkn8bVRt+Ksi+pMS7YeH694a ZoX9z2jMbBXR1L4SDKqmKS5JCAUYjqZAh743MXKpJI1xDBVVcxNIu1gz691FGdb02HEZ qFlepIIdQt4O9A03QoiHyhNBWFnH0JjTigxT5ff11M4neLe2sijBs+YI+1i8HDnwrvMm HmCwPrZ9Y5z+Sm4gG10Z+rGslnUXtLZfJ2OFdUHwaqzzBi7IKEIM65DjG/AygwZbttGN HrbA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763370338; x=1763975138; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=4Isv7N56R7GLLyxOJRI4a7jcx/LSO3hI5hjNg+m1oWY=; b=L1XI5uVpS2os4tRwJluGSSlUw33PdpqEiywvAe38dyj6f9LjWKE1pzv9ir6PYSJf7O OCwp6yOSxYV8XX0Xk9y0nhE+7YlI+71zjNHtE2fbR5f7TVqm73QZX/ATt6n1OZeHPLao 7SEsczv2LYzKJlNYLLpsC4tqTIvdvjnAX35XgpMDOQPYW+KDgLcICfnnfJMNdTWjZ6yh EINQt4bxEriHBbkIQM2RQdioXhkwaS4u62WVplf+a7O/CtX7WAXNQuJ8NnZ2le81FYy4 KwnyHz5GrR99SOS8iLele/RIInbOKjccQWsUEawcZQoo6XpFjacnYWnAPsRxsutpEdtj r0Lw== X-Forwarded-Encrypted: i=1; AJvYcCUHUhBSuoMnJCs2urFQcv0ffAM0Bgsty+BrgfeoITKmoatuJW989DZHq2j+/Ze+wRJMrzEwDrWmoQccvvI=@vger.kernel.org X-Gm-Message-State: AOJu0YzevKER8WhG1MaBTMOTKN6Coip0jPWMbYmlJYW7ghwNnsO/z14M zOZnEbkgaPUp1/ZUN+qBuR69JCr5u9yTY0NM6t1b8mjeRFA7Lo3J6b10 X-Gm-Gg: ASbGncvHHQQsxMkbZPRf2IaFs6TJ2+sUE+1Qh8EUkjxVEzDmU+pe0PlvrCgHdQ8UEfj 5p84/PuiGfj7My9jPH/3uzZmOuVy7/AOKYyDUOrn4naDxD0cVfFkhF/PJECM5XryJ4vRhU7povT 3fbqxszqEp9qiLKw1UItzRwhdhEn6d3uhaTmGxpBZkgjhIJlO34yrM7iUDFKs7YBwQc8tgX1+E/ qzyufWUnHHwggVGGAZLmxg/eBj1/B2vAhw9uOvfS7O9B9s6+kY2+KVHB+HBuiBOYiwp3Emr7PLw 5mA1/7QvmlMPREq06BL124NlE7BKCCw9k1iUknbGxx00nMvUJlRnl/NXaaIW91Qphbya6VrZuyU kkYtHa31qhZsJv5iKP09CcVexT+T9GA7l/NSqsmPUr03jvtmrBr8NtNfECe2OxltxFRxIbenUbY wge+Rj0mH5qdduHN9NjKSdu/Skh2LJ2UZzNGHxxUtXnHqM4cIHEkr5bfOm X-Google-Smtp-Source: AGHT+IFq3bjLXgXR/l8QI5stld2SKVhIi0p87VE5kMCDBF+BlgvJCbu8ZfTkjkZ+90HinEcjUZaNbA== X-Received: by 2002:a05:6512:3b9f:b0:594:4b7f:f946 with SMTP id 2adb3069b0e04-595841f981emr4477997e87.33.1763370337911; Mon, 17 Nov 2025 01:05:37 -0800 (PST) Received: from [192.168.1.168] (83-233-6-197.cust.bredband2.com. [83.233.6.197]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-59580405693sm3023727e87.76.2025.11.17.01.05.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 17 Nov 2025 01:05:36 -0800 (PST) From: Marcus Folkesson Date: Mon, 17 Nov 2025 10:04:59 +0100 Subject: [PATCH RESEND v3 2/5] i2c: mux: add support for per channel bus frequency Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20251117-i2c-mux-v3-2-696c83e3505a@gmail.com> References: <20251117-i2c-mux-v3-0-696c83e3505a@gmail.com> In-Reply-To: <20251117-i2c-mux-v3-0-696c83e3505a@gmail.com> To: Wolfram Sang , Peter Rosin , Michael Hennerich , Bartosz Golaszewski , Andi Shyti Cc: linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, Marcus Folkesson X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=7461; i=marcus.folkesson@gmail.com; h=from:subject:message-id; bh=o4Zt8X+zyI6wUYVNms/IgvHSL1cE9RhNZwSvJA7JohU=; b=owEBbQKS/ZANAwAKAYiATm9ZXVIyAcsmYgBpGuVKENJzVneUYEFrwizPAt/EAZgk8ypOAZ42j uXyyfqJ9DeJAjMEAAEKAB0WIQQFUaLotmy1TWTBLGWIgE5vWV1SMgUCaRrlSgAKCRCIgE5vWV1S MpIMD/0fxUebzVqxVVOGqSGh8svawNypcAjs9Vr7oeAwSxK6XVUDHoPmWgE5VNm1JHyC6cPIPhP huRtbhxKUIF+n9UoHGbeXPrR5oR+KWXbzofEs+OfY3tFS0ITmkBOkvHoVGwn502PObbn3b+q+/b 8Le+qjwaAh9y0Th+Kt69iLQ+NDSmP7Idt1Nr8JWPek0PRAXBVlJbxwIlHS6OYcO8a+HgrkHcqYi FDRLrwJnXYJZBFgsKR6Ujrl2l5yojDL+IQiTHpAQOXRvmo9cYHSJGgN5QH4mqb96xjsFnaNoldc ly6R7Ep5l6Klzrfnj5EdPXJUMvGOda22jBcZ31u368R60Zbxchune5kfw82d1ai5jwEtIuEMUx5 vGQqLpWKxjj2zSiRnRO8jzekfBCucrzhFQoTHx1AkC1zBLxDK3w8DvSlHP6vDNUs+VtxCKJUL0m dzPdcgsaKm4ltmQPw+E9eDDid6dLG6vQBk3k8obC13ksvnAdOXPrOFYUHbFmRNIEFOn8ecpe1xg 73VY7CmzZx6d1Vgo7fprBuia3W0953PUbnZ30iDSDeB7wV+PDR80L7RerYjayywC9fXehxbx4nt fDLvlj9mzn+nfbgoRdlLgqNFpRNtwzwj0Qq7RMibSRuwY3pMGT+kuIzXOjNDIh7wBeP2sCyiuXE DYxno5+lE/DABLQ== X-Developer-Key: i=marcus.folkesson@gmail.com; a=openpgp; fpr=AB91D46C7E0F6E6FB2AB640EC0FE25D598F6C127 There may be several reasons why you may need to use a certain speed on an I2C bus. E.g. - When several devices are attached to the bus, the speed must be selected according to the slowest device. - Electrical conditions may limit the usuable speed on the bus for different reasons. With an I2C multiplexer, it is possible to group the attached devices after their preferred speed by e.g. put all "slow" devices on a separate channel on the multiplexer. Consider the following topology: .----------. 100kHz .--------. .--------. 400kHz | |--------| dev D1 | | root |--+-----| I2C MUX | '--------' '--------' | | |--. 400kHz .--------. | '----------' '-------| dev D2 | | .--------. '--------' '--| dev D3 | '--------' One requirement with this design is that a multiplexer may only use the same or lower bus speed as its parent. Otherwise, if the multiplexer would have to increase the bus frequency, then all siblings (D3 in this case) would run into a clock speed it may not support. The bus frequency for each channel is set in the devicetree. As the i2c-mux bindings import the i2c-controller schema, the clock-frequency property is already allowed. If no clock-frequency property is set, the channel inherit their parent bus speed. The following example uses dt bindings to illustrate the topology above: i2c { clock-frequency = <400000>; i2c-mux { i2c@0 { clock-frequency = <100000>; D1 { ... }; }; i2c@1 { D2 { ... }; }; }; D3 { ... } }; Signed-off-by: Marcus Folkesson --- drivers/i2c/i2c-mux.c | 116 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 4d8690981a55..67ba33a13dde 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -36,6 +36,72 @@ struct i2c_mux_priv { u32 chan_id; }; +static int i2c_mux_select_chan(struct i2c_adapter *adap, u32 chan_id) +{ + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; + struct i2c_adapter *root; + int ret; + + if (priv->adap.clock_hz && priv->adap.clock_hz != parent->clock_hz) { + root = i2c_root_adapter(&adap->dev); + + /* if we are parent-locked and the root adapter is our parent, + * we already have the lock we need. Otherwise take the bus lock for the root + * adaper before changing bus clock. + */ + if ((root != parent && !muxc->mux_locked) || muxc->mux_locked) + i2c_lock_bus(parent, I2C_LOCK_ROOT_ADAPTER); + + ret = i2c_adapter_set_clk_freq(root, priv->adap.clock_hz); + + if ((root != parent && !muxc->mux_locked) || muxc->mux_locked) + i2c_unlock_bus(parent, I2C_LOCK_ROOT_ADAPTER); + + if (ret < 0) { + dev_err(&adap->dev, + "Failed to set clock frequency %dHz on root adapter %s: %d\n", + priv->adap.clock_hz, root->name, ret); + + return ret; + } + } + + return muxc->select(muxc, priv->chan_id); +} + +static void i2c_mux_deselect_chan(struct i2c_adapter *adap, u32 chan_id) +{ + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; + struct i2c_adapter *root; + int ret; + + if (parent->clock_hz && parent->clock_hz != priv->adap.clock_hz) { + root = i2c_root_adapter(&parent->dev); + + /* if we are parent-locked and the root adapter is our parent, + * we already have the lock we need. Otherwise take the bus lock for the root + * adaper before changing bus clock. + */ + if ((root != parent && !muxc->mux_locked) || muxc->mux_locked) + i2c_lock_bus(parent, I2C_LOCK_ROOT_ADAPTER); + + ret = i2c_adapter_set_clk_freq(root, parent->clock_hz); + + if ((root != parent && !muxc->mux_locked) || muxc->mux_locked) + i2c_unlock_bus(parent, I2C_LOCK_ROOT_ADAPTER); + + if (ret < 0) + return; + } + + if (muxc->deselect) + muxc->deselect(muxc, priv->chan_id); +} + static int __i2c_mux_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { @@ -46,11 +112,11 @@ static int __i2c_mux_master_xfer(struct i2c_adapter *adap, /* Switch to the right mux port and perform the transfer. */ - ret = muxc->select(muxc, priv->chan_id); + ret = i2c_mux_select_chan(adap, priv->chan_id); if (ret >= 0) ret = __i2c_transfer(parent, msgs, num); - if (muxc->deselect) - muxc->deselect(muxc, priv->chan_id); + + i2c_mux_deselect_chan(adap, priv->chan_id); return ret; } @@ -65,11 +131,11 @@ static int i2c_mux_master_xfer(struct i2c_adapter *adap, /* Switch to the right mux port and perform the transfer. */ - ret = muxc->select(muxc, priv->chan_id); + ret = i2c_mux_select_chan(adap, priv->chan_id); if (ret >= 0) ret = i2c_transfer(parent, msgs, num); - if (muxc->deselect) - muxc->deselect(muxc, priv->chan_id); + + i2c_mux_deselect_chan(adap, priv->chan_id); return ret; } @@ -86,12 +152,12 @@ static int __i2c_mux_smbus_xfer(struct i2c_adapter *adap, /* Select the right mux port and perform the transfer. */ - ret = muxc->select(muxc, priv->chan_id); + ret = i2c_mux_select_chan(adap, priv->chan_id); if (ret >= 0) ret = __i2c_smbus_xfer(parent, addr, flags, read_write, command, size, data); - if (muxc->deselect) - muxc->deselect(muxc, priv->chan_id); + + i2c_mux_deselect_chan(adap, priv->chan_id); return ret; } @@ -108,12 +174,12 @@ static int i2c_mux_smbus_xfer(struct i2c_adapter *adap, /* Select the right mux port and perform the transfer. */ - ret = muxc->select(muxc, priv->chan_id); + ret = i2c_mux_select_chan(adap, priv->chan_id); if (ret >= 0) ret = i2c_smbus_xfer(parent, addr, flags, read_write, command, size, data); - if (muxc->deselect) - muxc->deselect(muxc, priv->chan_id); + + i2c_mux_deselect_chan(adap, priv->chan_id); return ret; } @@ -365,6 +431,32 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, } } + of_property_read_u32(child, "clock-frequency", &priv->adap.clock_hz); + + /* + * Warn if the mux adapter is not parent-locked as + * this may cause issues for some hardware topologies. + */ + if ((priv->adap.clock_hz < parent->clock_hz) && muxc->mux_locked) + dev_warn(muxc->dev, + "channel %u is slower than parent on a non parent-locked mux\n", + chan_id); + + /* If the mux adapter has no clock-frequency property, inherit from parent */ + if (!priv->adap.clock_hz) + priv->adap.clock_hz = parent->clock_hz; + + /* We don't support mux adapters faster than their parent */ + if (priv->adap.clock_hz > parent->clock_hz) { + dev_err(muxc->dev, + "channel (%u) is faster (%u) than parent (%u)\n", + chan_id, priv->adap.clock_hz, parent->clock_hz); + + of_node_put(mux_node); + ret = -EINVAL; + goto err_free_priv; + } + priv->adap.dev.of_node = child; of_node_put(mux_node); } -- 2.51.2