{"id":2230099,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2230099/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-i2c/patch/20260429-k1-i2c-ilcr-v6-1-1c7a5a5a8b24@linux.spacemit.com/","project":{"id":35,"url":"http://patchwork.ozlabs.org/api/1.1/projects/35/?format=json","name":"Linux I2C development","link_name":"linux-i2c","list_id":"linux-i2c.vger.kernel.org","list_email":"linux-i2c@vger.kernel.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260429-k1-i2c-ilcr-v6-1-1c7a5a5a8b24@linux.spacemit.com>","date":"2026-04-29T07:48:56","name":"[v6,1/2] i2c: spacemit: configure ILCR/IWCR for accurate SCL frequency","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"a611d80ee2c92769542a256e95581bba04314cbe","submitter":{"id":91240,"url":"http://patchwork.ozlabs.org/api/1.1/people/91240/?format=json","name":"Troy Mitchell","email":"troy.mitchell@linux.spacemit.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/linux-i2c/patch/20260429-k1-i2c-ilcr-v6-1-1c7a5a5a8b24@linux.spacemit.com/mbox/","series":[{"id":502010,"url":"http://patchwork.ozlabs.org/api/1.1/series/502010/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-i2c/list/?series=502010","date":"2026-04-29T07:48:57","name":"i2c: spacemit: improve clock handling and cleanups","version":6,"mbox":"http://patchwork.ozlabs.org/series/502010/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2230099/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2230099/checks/","tags":{},"headers":{"Return-Path":"\n <linux-i2c+bounces-17197-incoming=patchwork.ozlabs.org@vger.kernel.org>","X-Original-To":["incoming@patchwork.ozlabs.org","linux-i2c@vger.kernel.org"],"Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=linux.spacemit.com header.i=@linux.spacemit.com\n header.a=rsa-sha256 header.s=mxsw2412 header.b=mbJtaNcJ;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c0a:e001:db::12fc:5321; helo=sea.lore.kernel.org;\n envelope-from=linux-i2c+bounces-17197-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)","smtp.subspace.kernel.org;\n\tdkim=pass (1024-bit key) header.d=linux.spacemit.com\n header.i=@linux.spacemit.com header.b=\"mbJtaNcJ\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=54.204.34.130","smtp.subspace.kernel.org;\n dmarc=none (p=none dis=none) header.from=linux.spacemit.com","smtp.subspace.kernel.org;\n spf=none smtp.mailfrom=linux.spacemit.com"],"Received":["from sea.lore.kernel.org (sea.lore.kernel.org\n [IPv6:2600:3c0a:e001:db::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g58hD2tDWz1xqf\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 17:54:04 +1000 (AEST)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id 4598B301AA5F\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 07:50:03 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id DF47E3A4F31;\n\tWed, 29 Apr 2026 07:50:01 +0000 (UTC)","from smtpbguseast2.qq.com (smtpbguseast2.qq.com [54.204.34.130])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 7728C3822BE;\n\tWed, 29 Apr 2026 07:49:58 +0000 (UTC)","from = ( [120.237.158.181])\n\tby bizesmtp.qq.com (ESMTP) with\n\tid ; Wed, 29 Apr 2026 15:49:01 +0800 (CST)"],"ARC-Seal":"i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777449001; cv=none;\n b=cZmicz/dKY23peWzWsFFdhKB2Y4NzrTfUNw53i0+YHwJfkoDZdvTFBMKegW+cvu5TYeip1m2W0VfCSKemtWEjWKrMy/ygrHHYZDhmUEvJMWH02mSqd3/SOnhXUEVrzWsoEb5jqjtU3BPAH82YxSqrTjTtTaasbDe41MkXnyqMvk=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777449001; c=relaxed/simple;\n\tbh=2vZgSG3fz1sfvmpztlC/YNa0AwJeX+tyJ+NMJkqoBMA=;\n\th=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References:\n\t In-Reply-To:To:Cc;\n b=AvBwp05O71TCBlG6kDYLgoc0J3mfhzamWbauUSDCO2M9Y+Iq6BjLoEd+Nn4UmX+t77bXTH1e7JAhMuyQodBiFfOwOspYp+vAbZvcM9UIyvYHVPu/3ZGMzRXft/gOTAUZMSfMAzCPNYGz1hAm1lkHORZNuOd+eqiE5VkgJgTcOfM=","ARC-Authentication-Results":"i=1; smtp.subspace.kernel.org;\n dmarc=none (p=none dis=none) header.from=linux.spacemit.com;\n spf=none smtp.mailfrom=linux.spacemit.com;\n dkim=pass (1024-bit key) header.d=linux.spacemit.com\n header.i=@linux.spacemit.com header.b=mbJtaNcJ;\n arc=none smtp.client-ip=54.204.34.130","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.spacemit.com;\n\ts=mxsw2412; t=1777448950;\n\tbh=tYItqWFcvtrzlS7Ntudj1s5aDXm6FyaHVjowJe67UF0=;\n\th=From:Date:Subject:MIME-Version:Message-Id:To;\n\tb=mbJtaNcJPWGwnswP4Y3njcxzs+txX5k/ml/ms4S1ZXDXzJw9hDA8Ilh7ZamQQDfRk\n\t EbQ85r9MneRpUPeR6Lem5Fy1XMa34taG5BryuKROLVuGWcK3zi67699johRPFS9K47\n\t sJM3gqDMHxaQNULP2GgOkS25+Pbxlw5e2Ago358U=","X-QQ-mid":"zesmtpgz1t1777448943t66e499f6","X-QQ-Originating-IP":"2IPYQQm+7cZBVpNEEPOOpSlM/HqOGLcWHnHTTIgCFYM=","X-QQ-SSF":"0000000000000000000000000000000","X-QQ-GoodBg":"0","X-BIZMAIL-ID":"5682307591694794909","EX-QQ-RecipientCnt":"8","From":"Troy Mitchell <troy.mitchell@linux.spacemit.com>","Date":"Wed, 29 Apr 2026 15:48:56 +0800","Subject":"[PATCH v6 1/2] i2c: spacemit: configure ILCR/IWCR for accurate SCL\n frequency","Precedence":"bulk","X-Mailing-List":"linux-i2c@vger.kernel.org","List-Id":"<linux-i2c.vger.kernel.org>","List-Subscribe":"<mailto:linux-i2c+subscribe@vger.kernel.org>","List-Unsubscribe":"<mailto:linux-i2c+unsubscribe@vger.kernel.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"7bit","Message-Id":"<20260429-k1-i2c-ilcr-v6-1-1c7a5a5a8b24@linux.spacemit.com>","References":"<20260429-k1-i2c-ilcr-v6-0-1c7a5a5a8b24@linux.spacemit.com>","In-Reply-To":"<20260429-k1-i2c-ilcr-v6-0-1c7a5a5a8b24@linux.spacemit.com>","To":"Andi Shyti <andi.shyti@kernel.org>, Alex Elder <elder@riscstar.com>,\n Yixun Lan <dlan@kernel.org>, Yixun Lan <dlan@kernel.org>","Cc":"linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org,\n linux-riscv@lists.infradead.org, spacemit@lists.linux.dev,\n Troy Mitchell <troy.mitchell@linux.spacemit.com>","X-Mailer":"b4 0.15.2","X-Developer-Signature":"v=1; a=ed25519-sha256; t=1777448937; l=12531;\n i=troy.mitchell@linux.spacemit.com; s=20250710; h=from:subject:message-id;\n bh=2vZgSG3fz1sfvmpztlC/YNa0AwJeX+tyJ+NMJkqoBMA=;\n b=RQZ9V921XGPsvy8WaG35HU19BEzB8OU/CZpdJiP+ANMOxLxY4bk243pTT4ipCnW7WVFbjyEa0\n ytu9sSKtGvoBBQQ1kkka7FpqZ1d3KvJBxcFOxne6sr8CeSfpMR7FC4L","X-Developer-Key":"i=troy.mitchell@linux.spacemit.com; a=ed25519;\n pk=lQa7BzLrq8DfZnChqmwJ5qQk8fP2USmY/4xZ2/MSsXc=","X-QQ-SENDSIZE":"520","Feedback-ID":"zesmtpgz:linux.spacemit.com:qybglogicsvrgz:qybglogicsvrgz3a-0","X-QQ-XMAILINFO":"N8jK27eo0USIaws1mTQTfzXt8E13YvK62LCrD6jc+PWjzWsqK/dnsgfB\n\tsdWYQon/GpRj7G2avUxvqRTlnyBDi8uBHNQFp/c16IksRn4LVX1IU9PHOBJdk+u689kkTRq\n\t2nhNbEA4iVw60cAj4Ii89tV/0uU7S/MRy+Xxe4FbVL+ro/MVbo+iPhRf1yeaue4YMNXVWQx\n\tnnQpC0Qb4yIMEL3E/5bXMNqQ0CEBmXqcN4tR0nXAWd4lBlhWRO/D4F6+PwSAXhcvN4urAjd\n\teEikjU/YMRl0joAYmxUEUcu2SSoGQE5Y3mbSgA2YHigHB5/XlgEt4jE22m1TxGcPcmDYCTi\n\tb5DeSEPj7AsEQVU6lVA3pB1uIqnE5u18LxvQh8KoW7lWTqH1ST3HvEneQQxNCok3paC0wNv\n\tYPxQreRtn/NayRNol55qElJDEz9NA14MxgEOitN1DERLYg9GjpQTCafeqLcqMkFx+Jj8jeh\n\tedJ34SVI0+MnkpTF9f/xOg2y5A3lOhU6o2TQtkJqh45yZJZVKL/v2vRdKGH/vTNKQQzQGFe\n\tcPC/ThPqBdr7t/QQRwZDuSo5DZmnUTwTdR92eRSXekgAvrk7fnaOOj1Y1NUviByv9lH4HlT\n\th6emWIEEPPJAXdqiLvTDBlx/CQHmq1x+abXNVrHyfinkzBHpevBaQtM1Af81C0nqfj0aeXh\n\tETKXayHpnCFtC7g9p9yItsisdgGJaE4xLgWu1jpQ2I8cxxHZ2rnKLGZuz1bB4PosNfrP471\n\tGM5XfG9nWOeETaeYjAyWzox1hy26buicI+ogW17Ij8CRP6Y8DcxRwybEMtQIXpll9bGXaa8\n\tWeA7iBU9/6qfkEfLfWaXaKqzx9Zn7k3f32FCthzM3byusxl6jPOghyEnl8LSDVIYseNQNLY\n\tDoY5rzg2Lc5wnsyCDkNqSBJIXachANcqSnp4WWG8jAAZoT2rDwcWhj4Fbe5vO3McW0iBDjn\n\tJJLrOyxS2DrGYnv5MkdX3JWV9Q49ZTrcKTyXowHTa9snaI3s47JC4XIchYjmTfCg9Fn3ssR\n\tTJdQdkBa96HgHV4IOPm1oMJyJk3+AMUv1x7iJd0bzHLNanOA1rDmFC87wd+6+LL2QwfYAR5\n\tor1WnQt7/2x9b66WXB8j3Wqk54BaI7JzolHYFJP8Xc2","X-QQ-XMRINFO":"OD9hHCdaPRBwH5bRRRw8tsiH4UAatJqXfg==","X-QQ-RECHKSPAM":"0"},"content":"The SpacemiT I2C controller's SCL (Serial Clock Line) frequency for\nmaster mode operations is determined by the ILCR (I2C Load Count Register).\nPreviously, the driver relied on the hardware's reset default\nvalues for this register.\n\nThe hardware's default ILCR values (SLV=0x156, FLV=0x5d) yield SCL\nfrequencies lower than intended. For example, with the default\n31.5 MHz input clock, these default settings result in an SCL\nfrequency of approximately 93 kHz (standard mode) when targeting 100 kHz,\nand approximately 338 kHz (fast mode) when targeting 400 kHz.\nThese frequencies are below the 100 kHz/400 kHz nominal speeds.\n\nThis patch integrates the SCL frequency management into\nthe Common Clock Framework (CCF). Specifically, the ILCR register,\nwhich acts as a frequency divider for the SCL clock, is now registered\nas a managed clock (scl_clk) within the CCF.\n\nThe actual hardware timing formulas are:\n- standard mode: SCL = FCLK / (2 * SLV + 8)\n- fast mode:     SCL = FCLK / (2 * FLV + 10)\n\nThese formulas are only valid when the IWCR (Wait Count Register) is\nprogrammed to 0x142A, a value specified by the I2C IP designer. The\ndriver now initializes IWCR to this value during controller init.\n\nReviewed-by: Yixun Lan <dlan@gentoo.org>\nSigned-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>\n---\nChangelog in v6:\n- fix SCL frequency calculation to match hardware timing formulas\n  (SCL = FCLK / (2*SLV+8) for standard, FCLK / (2*FLV+10) for fast)\n- initialize IWCR to 0x142A during init(required by I2C IP for\n  correct SCL timing)\n- use DIV_ROUND_CLOSEST() instead of DIV_ROUND_UP() for more accurate frequency\n- use field_prep() instead of manual bit shift for ILCR register programming\n- replace .round_rate with .determine_rate (round_rate was removed from clk_ops)\n- remove _MAX_VALUE macros (no longer needed after removing max_lv validation)\n- remove redundant max_lv validation in set_rate (determine_rate guarantees valid values)\n- simplify scl_clk_disable_unprepare callback to take clk pointer directly\n- remove unused parent parameter from spacemit_i2c_register_scl_clk()\n- remove stale description about whitespace cleanup from commit message\n- Link to v5: https://lore.kernel.org/r/20251226-k1-i2c-ilcr-v5-0-b5807b7dd0e6@linux.spacemit.com\n\nChangelog in v5:\n- use __ffs() instead of *_SHIFT\n- remove useless *_SHIFT\n- check return value when scl clk name array is truncated\n- rebase to v6.19-rc1\n- Link to v3: https://lore.kernel.org/all/20251017-k1-i2c-ilcr-v4-1-eed4903ecdb9@linux.spacemit.com/\n\nChangelog in v4:\n- initialize clk_init_data with {} so that init.flags is implicitly set to 0\n- minor cleanup and style fixes for better readability\n- remove unused spacemit_i2c_scl_clk_exclusive_put() cleanup callback\n- replace clk_set_rate_exclusive()/clk_rate_exclusive_put() pair with clk_set_rate()\n- simplify LCR LV field macros by using FIELD_GET/FIELD_MAX helpers\n- Link to v3: https://lore.kernel.org/all/20250814-k1-i2c-ilcr-v3-1-317723e74bcd@linux.spacemit.com/\n\nChangelog in v3:\n- use MASK macro in `recalc_rate` function\n- rename clock name\n- Link to v2: https://lore.kernel.org/r/20250718-k1-i2c-ilcr-v2-1-b4c68f13dcb1@linux.spacemit.com\n\nChangelog in v2:\n- Align line breaks.\n- Check `lv` in `clk_set_rate` function.\n- Force fast mode when SCL frequency is illegal or unavailable.\n- Change \"linux/bits.h\" to <linux/bits.h>\n- Kconfig: Add dependency on CCF.\n- Link to v1: https://lore.kernel.org/all/20250710-k1-i2c-ilcr-v1-1-188d1f460c7d@linux.spacemit.com/\n---\n drivers/i2c/busses/Kconfig  |   2 +-\n drivers/i2c/busses/i2c-k1.c | 160 +++++++++++++++++++++++++++++++++++++++++---\n 2 files changed, 153 insertions(+), 9 deletions(-)","diff":"diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig\nindex 8c935f867a37..89898fff1967 100644\n--- a/drivers/i2c/busses/Kconfig\n+++ b/drivers/i2c/busses/Kconfig\n@@ -793,7 +793,7 @@ config I2C_JZ4780\n config I2C_K1\n \ttristate \"SpacemiT K1 I2C adapter\"\n \tdepends on ARCH_SPACEMIT || COMPILE_TEST\n-\tdepends on OF\n+\tdepends on OF && COMMON_CLK\n \thelp\n \t  This option enables support for the I2C interface on the SpacemiT K1\n \t  platform.\ndiff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c\nindex 9152cf436bea..fad6a20bb43d 100644\n--- a/drivers/i2c/busses/i2c-k1.c\n+++ b/drivers/i2c/busses/i2c-k1.c\n@@ -4,7 +4,9 @@\n  */\n \n #include <linux/bitfield.h>\n+#include <linux/bits.h>\n #include <linux/clk.h>\n+#include <linux/clk-provider.h>\n #include <linux/i2c.h>\n #include <linux/iopoll.h>\n #include <linux/module.h>\n@@ -17,6 +19,8 @@\n #define SPACEMIT_ISR\t\t 0x4\t\t/* Status register */\n #define SPACEMIT_IDBR\t\t 0xc\t\t/* Data buffer register */\n #define SPACEMIT_IRCR\t\t 0x18\t\t/* Reset cycle counter */\n+#define SPACEMIT_ILCR\t\t 0x10\t\t/* Load Count Register */\n+#define SPACEMIT_IWCR\t\t 0x14\t\t/* Wait Count Register */\n #define SPACEMIT_IBMR\t\t 0x1c\t\t/* Bus monitor register */\n \n /* SPACEMIT_ICR register fields */\n@@ -88,6 +92,12 @@\n #define SPACEMIT_BMR_SDA         BIT(0)\t\t/* SDA line level */\n #define SPACEMIT_BMR_SCL         BIT(1)\t\t/* SCL line level */\n \n+#define SPACEMIT_LCR_LV_STANDARD_MASK\t\tGENMASK(8, 0)\n+#define SPACEMIT_LCR_LV_FAST_MASK\t\tGENMASK(17, 9)\n+\n+/* Required by I2C IP for correct SCL timing */\n+#define SPACEMIT_IWCR_INIT_VALUE\t\t0x142A\n+\n /* i2c bus recover timeout: us */\n #define SPACEMIT_I2C_BUS_BUSY_TIMEOUT\t\t100000\n \n@@ -109,11 +119,20 @@ enum spacemit_i2c_state {\n \tSPACEMIT_STATE_WRITE,\n };\n \n+enum spacemit_i2c_mode {\n+\tSPACEMIT_MODE_STANDARD,\n+\tSPACEMIT_MODE_FAST\n+};\n+\n /* i2c-spacemit driver's main struct */\n struct spacemit_i2c_dev {\n \tstruct device *dev;\n \tstruct i2c_adapter adapt;\n \n+\tstruct clk_hw scl_clk_hw;\n+\tstruct clk *scl_clk;\n+\tenum spacemit_i2c_mode mode;\n+\n \t/* hardware resources */\n \tvoid __iomem *base;\n \tint irq;\n@@ -135,6 +154,82 @@ struct spacemit_i2c_dev {\n \tu32 status;\n };\n \n+static void spacemit_i2c_scl_clk_disable_unprepare(void *data)\n+{\n+\tclk_disable_unprepare(data);\n+}\n+\n+static int spacemit_i2c_clk_set_rate(struct clk_hw *hw, unsigned long rate,\n+\t\t\t\t     unsigned long parent_rate)\n+{\n+\tstruct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);\n+\tu32 lv, lcr, mask, denom;\n+\n+\t/*\n+\t * Hardware timing formulas:\n+\t * - standard mode: SCL = FCLK / (2 * SLV + 8)\n+\t * - fast mode:     SCL = FCLK / (2 * FLV + 10)\n+\t */\n+\tdenom = DIV_ROUND_CLOSEST(parent_rate, rate);\n+\n+\tif (i2c->mode == SPACEMIT_MODE_STANDARD) {\n+\t\tmask = SPACEMIT_LCR_LV_STANDARD_MASK;\n+\t\tlv = (denom <= 8) ? 0 : DIV_ROUND_CLOSEST(denom - 8, 2);\n+\t} else {\n+\t\tmask = SPACEMIT_LCR_LV_FAST_MASK;\n+\t\tlv = (denom <= 10) ? 0 : DIV_ROUND_CLOSEST(denom - 10, 2);\n+\t}\n+\n+\tlcr = readl(i2c->base + SPACEMIT_ILCR);\n+\tlcr &= ~mask;\n+\tlcr |= field_prep(mask, lv);\n+\twritel(lcr, i2c->base + SPACEMIT_ILCR);\n+\n+\treturn 0;\n+}\n+\n+static int spacemit_i2c_clk_determine_rate(struct clk_hw *hw,\n+\t\t\t\t\t   struct clk_rate_request *req)\n+{\n+\tstruct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);\n+\tu32 lv, denom;\n+\n+\tdenom = DIV_ROUND_CLOSEST(req->best_parent_rate, req->rate);\n+\n+\tif (i2c->mode == SPACEMIT_MODE_STANDARD) {\n+\t\tlv = (denom <= 8) ? 0 : DIV_ROUND_CLOSEST(denom - 8, 2);\n+\t\treq->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + 8);\n+\t} else {\n+\t\tlv = (denom <= 10) ? 0 : DIV_ROUND_CLOSEST(denom - 10, 2);\n+\t\treq->rate = DIV_ROUND_CLOSEST(req->best_parent_rate, lv * 2 + 10);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static unsigned long spacemit_i2c_clk_recalc_rate(struct clk_hw *hw,\n+\t\t\t\t\t\t  unsigned long parent_rate)\n+{\n+\tstruct spacemit_i2c_dev *i2c = container_of(hw, struct spacemit_i2c_dev, scl_clk_hw);\n+\tu32 lcr, lv = 0;\n+\n+\tlcr = readl(i2c->base + SPACEMIT_ILCR);\n+\n+\tif (i2c->mode == SPACEMIT_MODE_STANDARD) {\n+\t\tlv = FIELD_GET(SPACEMIT_LCR_LV_STANDARD_MASK, lcr);\n+\t\treturn DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 8);\n+\t}\n+\n+\tlv = FIELD_GET(SPACEMIT_LCR_LV_FAST_MASK, lcr);\n+\treturn DIV_ROUND_CLOSEST(parent_rate, lv * 2 + 10);\n+}\n+\n+static const struct clk_ops spacemit_i2c_clk_ops = {\n+\t.set_rate = spacemit_i2c_clk_set_rate,\n+\t.determine_rate = spacemit_i2c_clk_determine_rate,\n+\t.recalc_rate = spacemit_i2c_clk_recalc_rate,\n+};\n+\n static void spacemit_i2c_enable(struct spacemit_i2c_dev *i2c)\n {\n \tu32 val;\n@@ -153,6 +248,28 @@ static void spacemit_i2c_disable(struct spacemit_i2c_dev *i2c)\n \twritel(val, i2c->base + SPACEMIT_ICR);\n }\n \n+static struct clk *spacemit_i2c_register_scl_clk(struct spacemit_i2c_dev *i2c)\n+{\n+\tstruct clk_init_data init = {};\n+\tchar name[64];\n+\tint ret;\n+\n+\tret = snprintf(name, sizeof(name), \"%s_scl_clk\", dev_name(i2c->dev));\n+\tif (ret >= ARRAY_SIZE(name))\n+\t\tdev_warn(i2c->dev, \"scl clock name truncated\");\n+\n+\tinit.name = name;\n+\tinit.ops = &spacemit_i2c_clk_ops;\n+\tinit.parent_data = (struct clk_parent_data[]) {\n+\t\t{ .fw_name = \"func\" },\n+\t};\n+\tinit.num_parents = 1;\n+\n+\ti2c->scl_clk_hw.init = &init;\n+\n+\treturn devm_clk_register(i2c->dev, &i2c->scl_clk_hw);\n+}\n+\n static void spacemit_i2c_reset(struct spacemit_i2c_dev *i2c)\n {\n \twritel(SPACEMIT_CR_UR, i2c->base + SPACEMIT_ICR);\n@@ -286,7 +403,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c)\n \t\tval |= SPACEMIT_CR_MSDIE;\n \t}\n \n-\tif (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ)\n+\tif (i2c->mode == SPACEMIT_MODE_FAST)\n \t\tval |= SPACEMIT_CR_MODE_FAST;\n \n \t/* disable response to general call */\n@@ -309,6 +426,14 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c)\n \twritel(val, i2c->base + SPACEMIT_IRCR);\n \n \tspacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK);\n+\n+\t/*\n+\t * Initialize IWCR to the value specified by the I2C IP designer.\n+\t * The SCL frequency formulas (SCL = FCLK / (2*SLV+8) for standard\n+\t * mode, SCL = FCLK / (2*FLV+10) for fast mode) are only valid when\n+\t * IWCR contains this specific value.\n+\t */\n+\twritel(SPACEMIT_IWCR_INIT_VALUE, i2c->base + SPACEMIT_IWCR);\n }\n \n static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c)\n@@ -703,14 +828,15 @@ static int spacemit_i2c_probe(struct platform_device *pdev)\n \t\tdev_warn(dev, \"failed to read clock-frequency property: %d\\n\", ret);\n \n \t/* For now, this driver doesn't support high-speed. */\n-\tif (!i2c->clock_freq || i2c->clock_freq > SPACEMIT_I2C_MAX_FAST_MODE_FREQ) {\n-\t\tdev_warn(dev, \"unsupported clock frequency %u; using %u\\n\",\n-\t\t\t i2c->clock_freq, SPACEMIT_I2C_MAX_FAST_MODE_FREQ);\n+\tif (i2c->clock_freq > SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ &&\n+\t    i2c->clock_freq <= SPACEMIT_I2C_MAX_FAST_MODE_FREQ) {\n+\t\ti2c->mode = SPACEMIT_MODE_FAST;\n+\t} else if (i2c->clock_freq && i2c->clock_freq <= SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) {\n+\t\ti2c->mode = SPACEMIT_MODE_STANDARD;\n+\t} else {\n+\t\tdev_warn(i2c->dev, \"invalid clock-frequency, fallback to fast mode\");\n+\t\ti2c->mode = SPACEMIT_MODE_FAST;\n \t\ti2c->clock_freq = SPACEMIT_I2C_MAX_FAST_MODE_FREQ;\n-\t} else if (i2c->clock_freq < SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ) {\n-\t\tdev_warn(dev, \"unsupported clock frequency %u; using %u\\n\",\n-\t\t\t i2c->clock_freq,  SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ);\n-\t\ti2c->clock_freq = SPACEMIT_I2C_MAX_STANDARD_MODE_FREQ;\n \t}\n \n \ti2c->dev = &pdev->dev;\n@@ -732,6 +858,11 @@ static int spacemit_i2c_probe(struct platform_device *pdev)\n \tif (IS_ERR(clk))\n \t\treturn dev_err_probe(dev, PTR_ERR(clk), \"failed to enable func clock\");\n \n+\ti2c->scl_clk = spacemit_i2c_register_scl_clk(i2c);\n+\tif (IS_ERR(i2c->scl_clk))\n+\t\treturn dev_err_probe(&pdev->dev, PTR_ERR(i2c->scl_clk),\n+\t\t\t\t     \"failed to register scl clock\\n\");\n+\n \tclk = devm_clk_get_enabled(dev, \"bus\");\n \tif (IS_ERR(clk))\n \t\treturn dev_err_probe(dev, PTR_ERR(clk), \"failed to enable bus clock\");\n@@ -741,6 +872,19 @@ static int spacemit_i2c_probe(struct platform_device *pdev)\n \t\treturn dev_err_probe(dev, PTR_ERR(rst),\n \t\t\t\t     \"failed to acquire deasserted reset\\n\");\n \n+\tret = clk_set_rate(i2c->scl_clk, i2c->clock_freq);\n+\tif (ret)\n+\t\treturn dev_err_probe(&pdev->dev, ret, \"failed to set rate for SCL clock\");\n+\n+\tret = clk_prepare_enable(i2c->scl_clk);\n+\tif (ret)\n+\t\treturn dev_err_probe(&pdev->dev, ret, \"failed to prepare and enable clock\");\n+\n+\tret = devm_add_action_or_reset(dev, spacemit_i2c_scl_clk_disable_unprepare,\n+\t\t\t\t       i2c->scl_clk);\n+\tif (ret)\n+\t\treturn ret;\n+\n \tspacemit_i2c_reset(i2c);\n \n \ti2c_set_adapdata(&i2c->adapt, i2c);\n","prefixes":["v6","1/2"]}