mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "lianfeng.ouyang" <lianfeng.ouyang@starfivetech.com>
To: Andi Shyti <andi.shyti@kernel.org>, Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Mika Westerberg <mika.westerberg@linux.intel.com>,
	Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
	Jan Dabros <jsd@semihalf.com>
Cc: linux-i2c@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	Lianfeng Ouyang <lianfeng.ouyang@starfivetech.com>
Subject: [PATCH v1 3/3] i2c: dwc: Add StarFive JHB100 I2C master/slave support
Date: Thu, 21 May 2026 11:43:40 +0800	[thread overview]
Message-ID: <20260521034340.27837-4-lianfeng.ouyang@starfivetech.com> (raw)
In-Reply-To: <20260521034340.27837-1-lianfeng.ouyang@starfivetech.com>

From: Lianfeng Ouyang <lianfeng.ouyang@starfivetech.com>

Add support for the Synopsys DesignWare Core (DWC) I2C controller
found on the StarFive JHB100 platform. The controller supports both
master and slave modes and uses a different register layout compared
to the standard DesignWare I2C IP.

Key differences handled by this driver:
1.  All key registers have different offsets. The driver redefines
    these offsets in i2c-dwc-core.h.
2.  Interrupts are cleared by writing to a separate INTR_CLR register
    (write-1-to-clear), unlike the read-to-clear scheme of the
    standard IP.
3.  The IC_ENABLE register has an additional TX_CMD_BLOCK control bit
    which must be managed during enable/disable sequences.
4.  Master and slave require separate probe callbacks and cannot rely
    solely on the runtime mode switching provided by i2c_dw_set_mode().

The driver leverages the common i2c-designware-core infrastructure
exported by the previous patch, while implementing its own timing
calculation, FIFO configuration, and interrupt handling to accommodate
the DWC register offsets and bit definitions.

Signed-off-by: Lianfeng Ouyang <lianfeng.ouyang@starfivetech.com>
---
 MAINTAINERS                         |   7 +
 drivers/i2c/busses/Kconfig          |  34 +++
 drivers/i2c/busses/Makefile         |   3 +
 drivers/i2c/busses/i2c-dwc-core.h   | 184 ++++++++++++
 drivers/i2c/busses/i2c-dwc-master.c | 441 ++++++++++++++++++++++++++++
 drivers/i2c/busses/i2c-dwc-slave.c  | 180 ++++++++++++
 6 files changed, 849 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-dwc-core.h
 create mode 100644 drivers/i2c/busses/i2c-dwc-master.c
 create mode 100644 drivers/i2c/busses/i2c-dwc-slave.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d3a6b3f6b6a0..730388b94e56 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25677,6 +25677,13 @@ L:	linux-i2c@vger.kernel.org
 S:	Supported
 F:	drivers/i2c/busses/i2c-designware-*
 
+SYNOPSYS DWC I2C DRIVER
+M:	Lianfeng Ouyang <lianfeng.ouyang@starfivetech.com>
+L:	linux-i2c@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/i2c/snps,dwc-i2c.yaml
+F:	drivers/i2c/busses/i2c-dwc-*
+
 SYNOPSYS DESIGNWARE I2C DRIVER - AMDISP
 M:	Nirujogi Pratap <pratap.nirujogi@amd.com>
 M:	Bin Du <bin.du@amd.com>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7cb6b9b864a7..7775b4f40d88 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -640,6 +640,40 @@ config I2C_DESIGNWARE_PCI
 
 endif
 
+config I2C_DWC_CORE
+	tristate "Synopsys DWC I2C adapter"
+	select REGMAP
+	help
+	  This is a driver for the Synopsys DesignWare CORE (DWC) I2C controller,
+	  a variant of the DesignWare I2C IP with a different register layout.
+	  It shares common infrastructure with the standard DesignWare I2C
+	  driver (I2C_DESIGNWARE_CORE)
+
+if I2C_DWC_CORE
+
+config I2C_DWC_SLAVE
+	bool "Synopsys DWC Slave"
+	default I2C_DWC_CORE
+	select I2C_SLAVE
+	help
+	  If you say yes to this option, support will be included for the
+	  Synopsys DWC I2C slave adapter.
+
+	  This is not a standalone module, this module compiles together with
+	  i2c-dwc-core.
+
+config I2C_DWC_PLATFORM
+	tristate "Synopsys DWC Platform"
+	default I2C_DWC_CORE
+	help
+	  If you say yes to this option, support will be included for the
+	  Synopsys DWC I2C adapter.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-dwc-platform.
+
+endif
+
 config I2C_DIGICOLOR
 	tristate "Conexant Digicolor I2C driver"
 	depends on ARCH_DIGICOLOR || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 547123ab351f..3ed593011578 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,9 @@ i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_BAYTRAIL) += i2c-designware-bayt
 obj-$(CONFIG_I2C_DESIGNWARE_AMDISP) += i2c-designware-amdisp.o
 obj-$(CONFIG_I2C_DESIGNWARE_PCI)			+= i2c-designware-pci.o
 i2c-designware-pci-y					:= i2c-designware-pcidrv.o
+obj-$(CONFIG_I2C_DWC_CORE) 				+= i2c-dwc-core.o
+i2c-dwc-core-y                                     	+= i2c-dwc-master.o
+i2c-dwc-core-$(CONFIG_I2C_DWC_SLAVE) 			+= i2c-dwc-slave.o
 obj-$(CONFIG_I2C_DIGICOLOR)	+= i2c-digicolor.o
 obj-$(CONFIG_I2C_EG20T)		+= i2c-eg20t.o
 obj-$(CONFIG_I2C_EMEV2)		+= i2c-emev2.o
diff --git a/drivers/i2c/busses/i2c-dwc-core.h b/drivers/i2c/busses/i2c-dwc-core.h
new file mode 100644
index 000000000000..eba2eaf116c9
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-core.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Synopsys DWC I2C adapter driver.
+ *
+ * Based on the TI DAVINCI I2C adapter driver.
+ *
+ * Copyright (C) 2006 Texas Instruments.
+ * Copyright (C) 2007 MontaVista Software Inc.
+ * Copyright (C) 2009 Provigent Ltd.
+ */
+
+#include <linux/bits.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define IC_DFLT_OPERATION_REG_OFFSET		0x00
+#define IC_DFLT_I2C_REG_OFFSET			0x20
+#define IC_DFLT_SMBUS_REG_OFFSET		0xFC
+
+#define DW_IC_DEFAULT_FUNCTIONALITY		(I2C_FUNC_I2C | \
+						 I2C_FUNC_SMBUS_BYTE | \
+						 I2C_FUNC_SMBUS_BYTE_DATA | \
+						 I2C_FUNC_SMBUS_WORD_DATA | \
+						 I2C_FUNC_SMBUS_BLOCK_DATA | \
+						 I2C_FUNC_SMBUS_I2C_BLOCK)
+
+#define DWC_IC_CTRL_OP_MODE			BIT(0)
+#define DWC_IC_CTRL_SPEED_STD			BIT(4)
+#define DWC_IC_CTRL_SPEED_FAST			(2 << 4)
+#define DWC_IC_CTRL_SPEED_HIGH			(3 << 4)
+#define DWC_IC_CTRL_SPEED_MASK			GENMASK(5, 4)
+#define DWC_IC_CTRL_10BITADDR_TGT		BIT(8)
+#define DWC_IC_CTRL_10BITADDR_CTRLR		BIT(9)
+#define DWC_IC_CTRL_STOP_DET_IFADDRESSED	BIT(10)
+#define DWC_IC_CTRL_TX_EMPTY_CTRL		BIT(11)
+#define DWC_IC_CTRL_RX_FIFO_FULL_HLD_CTRL	BIT(12)
+#define DWC_IC_CTRL_BUS_CLEAR_CTRL		BIT(14)
+
+#define DWC_IC_DATA_CMD_DAT			GENMASK(7, 0)
+#define DWC_IC_DATA_CMD_FIRST_DATA_BYTE		BIT(11)
+#define DWC_IC_DATA_CMD_STOP			BIT(9)
+#define DWC_IC_DATA_CMD_CMD			BIT(8)
+
+/*
+ * Registers offset
+ */
+#undef DW_IC_ENABLE
+#define DW_IC_ENABLE				(IC_DFLT_OPERATION_REG_OFFSET + 0x04)
+#define DWC_IC_CAPABILITIES			(IC_DFLT_OPERATION_REG_OFFSET + 0x0c)
+#define DWC_IC_SMBUS_CAPABILITIES		(IC_DFLT_OPERATION_REG_OFFSET + 0x18)
+
+#define DWC_IC_CTRL				(IC_DFLT_I2C_REG_OFFSET + 0x04)
+#undef DW_IC_TAR
+#define DW_IC_TAR				(IC_DFLT_I2C_REG_OFFSET + 0x08)
+#define DWC_IC_DAR				(IC_DFLT_I2C_REG_OFFSET + 0x0C)
+#define DWC_IC_SCL_HCNT				(IC_DFLT_I2C_REG_OFFSET + 0x24)
+#define DWC_IC_SCL_LCNT				(IC_DFLT_I2C_REG_OFFSET + 0x28)
+#define DWC_IC_HS_SCL_HCNT			(IC_DFLT_I2C_REG_OFFSET + 0x2c)
+#define DWC_IC_HS_SCL_LCNT			(IC_DFLT_I2C_REG_OFFSET + 0x30)
+#undef DW_IC_SDA_HOLD
+#define DW_IC_SDA_HOLD				(IC_DFLT_I2C_REG_OFFSET + 0x34)
+#define DWC_IC_SPKLEN				(IC_DFLT_I2C_REG_OFFSET + 0x3c)
+#define DWC_IC_HS_SPKLEN			(IC_DFLT_I2C_REG_OFFSET + 0x40)
+#define DWC_IC_SCL_STUCK_AT_LOW_TIMEOUT		(IC_DFLT_I2C_REG_OFFSET + 0x44)
+#define DWC_IC_SCL_STUCK_AT_LOW_TIMEOUT_MAX	(IC_DFLT_I2C_REG_OFFSET + 0x48)
+#define DWC_IC_SDA_STUCK_AT_LOW_TIMEOUT		(IC_DFLT_I2C_REG_OFFSET + 0x4c)
+#undef DW_IC_DATA_CMD
+#define DW_IC_DATA_CMD				(IC_DFLT_I2C_REG_OFFSET + 0x58)
+#define DWC_IC_RX_TL				(IC_DFLT_I2C_REG_OFFSET + 0x5c)
+#define DWC_IC_TX_TL				(IC_DFLT_I2C_REG_OFFSET + 0x60)
+#undef DW_IC_INTR_STAT
+#define DW_IC_INTR_STAT				(IC_DFLT_I2C_REG_OFFSET + 0x74)
+#undef DW_IC_INTR_MASK
+#define DW_IC_INTR_MASK				(IC_DFLT_I2C_REG_OFFSET + 0x78)
+#undef DW_IC_RAW_INTR_STAT
+#define DW_IC_RAW_INTR_STAT			(IC_DFLT_I2C_REG_OFFSET + 0x7c)
+#define DWC_IC_INTR_CLR				(IC_DFLT_I2C_REG_OFFSET + 0x80)
+#define DWC_CLR_INTR				BIT(0)
+#define DWC_IC_CLR_RX_UNDER			BIT(1)
+#define DWC_IC_CLR_RX_OVER			BIT(2)
+#define DWC_IC_CLR_TX_OVER			BIT(3)
+#define DWC_IC_CLR_RD_REQ			BIT(4)
+#define DWC_IC_CLR_TX_ABRT			BIT(5)
+#define DWC_IC_CLR_RX_DONE			BIT(6)
+#define DWC_IC_CLR_ACTIVITY			BIT(7)
+#define DWC_IC_CLR_STOP_DET			BIT(8)
+#define DWC_IC_CLR_START_DET			BIT(9)
+#define DWC_IC_CLR_GEN_CALL			BIT(10)
+#define DWC_IC_CLR_RESTART_DET			BIT(11)
+#define DWC_IC_CLR_SCL_STUCK_DET		BIT(12)
+#undef DW_IC_ENABLE_STATUS
+#define DW_IC_ENABLE_STATUS			(IC_DFLT_I2C_REG_OFFSET + 0x84)
+#define DWC_IC_TX_TRMNT_SOURCE			(IC_DFLT_I2C_REG_OFFSET + 0x88)
+#undef DW_IC_STATUS
+#define DW_IC_STATUS				(IC_DFLT_I2C_REG_OFFSET + 0x8c)
+#undef DW_IC_TXFLR
+#define DW_IC_TXFLR				(IC_DFLT_I2C_REG_OFFSET + 0x90)
+#undef DW_IC_RXFLR
+#define DW_IC_RXFLR				(IC_DFLT_I2C_REG_OFFSET + 0x94)
+#define DW_IC_COMP_PARAM_1			0xf4
+#undef DW_IC_COMP_VERSION
+#define DW_IC_COMP_VERSION			(IC_DFLT_I2C_REG_OFFSET + 0xa4)
+#define DWC_IC_SDA_HOLD_MIN_VERS		0x3131312A /* "111*" == v1.11* */
+
+#undef DW_IC_COMP_TYPE
+#define DW_IC_COMP_TYPE				(IC_DFLT_I2C_REG_OFFSET + 0xa8)
+#define DWC_IC_INTR_SCL_STUCK_AT_LOW		BIT(14)
+
+#define DWC_IC_STATUS_ACTIVITY			BIT(0)
+#define DWC_IC_STATUS_TFE			BIT(2)
+#define DWC_IC_STATUS_RFNE			BIT(3)
+#define DWC_IC_STATUS_CTRLR_ACTIVITY		BIT(5)
+#define DWC_IC_STATUS_TGT_ACTIVITY		BIT(6)
+
+#define DW_IC_SDA_HOLD_TX_SHIFT			0
+#define DW_IC_SDA_HOLD_TX_MASK			GENMASK(15, 0)
+
+#define DW_IC_ERR_TX_ABRT			0x1
+
+#define DWC_IC_TAR_SMBUS_QUICK_CMD		BIT(16)
+#define DWC_IC_TAR_SPECIAL			BIT(11)
+
+#define DWC_IC_COMP_PARAM_1_SPEED_MODE_HIGH	(BIT(4) | BIT(5))
+#define DWC_IC_COMP_PARAM_1_SPEED_MODE_MASK	GENMASK(5, 4)
+
+#define DWC_IC_SMBUS_ARP_CTRL			(IC_DFLT_SMBUS_REG_OFFSET + 0x8)
+#define DWC_IC_SMBUS_INTR_STAT			(IC_DFLT_SMBUS_REG_OFFSET + 0x28)
+#define DWC_IC_SMBUS_INTR_CLR			(IC_DFLT_SMBUS_REG_OFFSET + 0x34)
+#define DWC_CLR_SMBUS_ALERT_DET			BIT(10)
+#define DWC_R_SMBUS_ALERT_DET			BIT(10)
+
+#define DWC_IC_DAR4_EN				19
+#define DWC_IC_DAR3_EN				18
+#define DWC_IC_DAR2_EN				17
+#define DWC_IC_DAR_EN				16
+
+#define DWC_IC_ENABLE_DAR_EN			BIT(DWC_IC_DAR_EN)
+#define DWC_IC_ENABLE_DAR2_EN			BIT(DWC_IC_DAR2_EN)
+#define DWC_IC_ENABLE_DAR3_EN			BIT(DWC_IC_DAR3_EN)
+#define DWC_IC_ENABLE_DAR4_EN			BIT(DWC_IC_DAR4_EN)
+
+#define DW_IC_ENABLE_TX_CMD_BLOCK		BIT(2)
+
+#define DWC_IC_SMBUS				10
+
+#define DWC_IC_CAPABILITIES_IC_SMBUS		BIT(DWC_IC_SMBUS)
+
+#define DWC_IC_SMBUS_ARP			2
+
+#define DWC_IC_SMBUS_CAPABILITIES_SMBUS_ARP	BIT(DWC_IC_SMBUS_ARP)
+
+#define DWC_IC_SMBUS_ARP_CTRL_NARP_DEVICE_TYPE	0
+
+#undef TXGBE_RX_FIFO_DEPTH
+#define TXGBE_RX_FIFO_DEPTH			0
+
+extern const struct i2c_algorithm i2c_dw_slave_algo;
+int i2c_dw_probe_slave(struct dw_i2c_dev *dev);
+int i2c_dw_probe_master(struct dw_i2c_dev *dev);
+void i2c_dw_read_clear_intrbits_common(struct dw_i2c_dev *dev);
+
+static inline void __i2c_dw_enable(struct dw_i2c_dev *dev)
+{
+	int val;
+
+	dev->status |= STATUS_ACTIVE;
+	regmap_read(dev->map, DW_IC_ENABLE, &val);
+	regmap_write(dev->map, DW_IC_ENABLE,
+		     ((val & ~DW_IC_ENABLE_TX_CMD_BLOCK) | DW_IC_ENABLE_ENABLE));
+}
+
+static inline void __i2c_dw_disable_nowait(struct dw_i2c_dev *dev)
+{
+	int val;
+
+	regmap_read(dev->map, DW_IC_ENABLE, &val);
+	regmap_write(dev->map, DW_IC_ENABLE, val & ~DW_IC_ENABLE_ENABLE);
+	dev->status &= ~STATUS_ACTIVE;
+}
diff --git a/drivers/i2c/busses/i2c-dwc-master.c b/drivers/i2c/busses/i2c-dwc-master.c
new file mode 100644
index 000000000000..6e8d1828f99e
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-master.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Synopsys DWC I2C adapter driver (master only).
+ *
+ * Based on the TI DAVINCI I2C adapter driver.
+ *
+ * Copyright (C) 2006 Texas Instruments.
+ * Copyright (C) 2007 MontaVista Software Inc.
+ * Copyright (C) 2009 Provigent Ltd.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#include "i2c-designware-core.h"
+
+#define AMD_TIMEOUT_MIN_US	25
+#define AMD_TIMEOUT_MAX_US	250
+#define AMD_MASTERCFG_MASK	GENMASK(15, 0)
+
+/**
+ * i2c_dwc_scl_hcnt() -  Calculate SCL HCNT
+ * @ic_clk: Input clock in kHz
+ * @thigh: Duration in ns of logic 1 to generate
+ * @tr: SCL rise time in ns
+ * @spk_cnt: Spike count
+ */
+static u32 i2c_dwc_scl_hcnt(u32 ic_clk, u32 thigh, u32 tr, u32 spk_cnt)
+{
+	u64 min_thigh_cnt, rise_cnt;
+
+	/* Formula: cnt = f_kHz * t_ns * 10^(-6) */
+	min_thigh_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * thigh, MICRO);
+	rise_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tr, MICRO);
+
+	return max(5, min_thigh_cnt + rise_cnt - spk_cnt - 3);
+}
+
+/**
+ * i2c_dwc_scl_lcnt() -  Calculate SCL LCNT
+ * @ic_clk: Input clock in kHz
+ * @tlow: Duration in ns of logic 0 to generate
+ * @tf: SCL fall time in ns
+ */
+static u32 i2c_dwc_scl_lcnt(u32 ic_clk, u32 tlow, u32 tf)
+{
+	u64 min_tlow_cnt, fall_cnt;
+
+	/* Formula: cnt = f_kHz * t_ns * 10^(-6) */
+	min_tlow_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tlow, MICRO);
+	fall_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tf, MICRO);
+
+	return max(6, min_tlow_cnt + fall_cnt);
+}
+
+static void i2c_dwc_configure_fifo_master(struct dw_i2c_dev *dev)
+{
+	/* Configure Tx/Rx FIFO threshold levels */
+	regmap_write(dev->map, DWC_IC_TX_TL, dev->tx_fifo_depth / 2);
+	regmap_write(dev->map, DWC_IC_RX_TL, 0);
+
+	/* Configure the I2C master */
+	regmap_write(dev->map, DWC_IC_CTRL, dev->master_cfg);
+}
+
+static int i2c_dwc_set_timings_master(struct dw_i2c_dev *dev)
+{
+	u32 scl_falling_time = 0, scl_rising_time = 0;
+	u32 scl_high_time = 0, scl_low_time = 0;
+	struct i2c_timings *t = &dev->timings;
+	unsigned int comp_param1;
+	u32 ic_clk, spk_cnt;
+	int ret;
+
+	ret = i2c_dw_acquire_lock(dev);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(dev->map, DWC_IC_CTRL, &comp_param1);
+	i2c_dw_release_lock(dev);
+	if (ret)
+		return ret;
+
+	ic_clk = i2c_dw_clk_rate(dev);
+
+	/* 50ns maximum spike */
+	spk_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * 50, MICRO);
+
+	regmap_write(dev->map, DWC_IC_HS_SPKLEN, spk_cnt);
+	regmap_write(dev->map, DWC_IC_SPKLEN, spk_cnt);
+
+	/* Parse user defined rise time and fall time*/
+	if (t->scl_rise_ns)
+		scl_rising_time = t->scl_rise_ns;
+
+	if (t->scl_fall_ns)
+		scl_falling_time = t->scl_fall_ns;
+
+	/* Ensure the rise time and fall time should not lower than t_rise_max
+	 * and t_fall_max specification, else it would run faster than expected
+	 * frequency
+	 */
+	switch (t->bus_freq_hz) {
+	case I2C_MAX_STANDARD_MODE_FREQ:
+		scl_rising_time = max(scl_rising_time, 1000);
+		scl_falling_time = max(scl_falling_time, 300);
+		scl_high_time = 4000;	/* tHIGH_min = 4.0 us */
+		scl_low_time = 4700;	/* tLOW_min = 4.7 us */
+		break;
+	case I2C_MAX_FAST_MODE_FREQ:
+		scl_rising_time = max(scl_rising_time, 300);
+		scl_falling_time = max(scl_falling_time, 300);
+		scl_high_time = 600;	/* tHIGH_min = 600 ns */
+		scl_low_time = 1300;	/* tLOW_min = 1.3 us */
+		break;
+	case I2C_MAX_FAST_MODE_PLUS_FREQ:
+		scl_rising_time = max(scl_rising_time, 120);
+		scl_falling_time = max(scl_falling_time, 120);
+		scl_high_time = 260;	/* tHIGH_min = 260 ns */
+		scl_low_time = 500;	/* tLOW_min = 500 ns */
+		break;
+	case I2C_MAX_HIGH_SPEED_MODE_FREQ:
+		scl_rising_time = max(scl_rising_time, 40);
+		scl_falling_time = max(scl_falling_time, 40);
+		scl_high_time = 60;	/* tHIGH_min = 60 ns */
+		scl_low_time = 160;	/* tLOW_min = 160 ns */
+		break;
+	default:
+		scl_rising_time = max(scl_rising_time, 1000);
+		scl_falling_time = max(scl_falling_time, 300);
+		scl_high_time = 4000;	/* tHIGH_min = 4.0 us */
+		scl_low_time = 4700;	/* tLOW_min = 4.7 us */
+		break;
+	}
+
+	ic_clk = i2c_dw_clk_rate(dev);
+
+	if (!dev->scl_hcnt || !dev->scl_lcnt) {
+		dev->scl_hcnt = i2c_dwc_scl_hcnt(ic_clk, scl_high_time,
+						 scl_rising_time, spk_cnt);
+		dev->scl_lcnt = i2c_dwc_scl_lcnt(ic_clk, scl_low_time,
+						 scl_falling_time);
+	}
+
+	dev_dbg(dev->dev, "Bus speed: %s\n", i2c_freq_mode_string(t->bus_freq_hz));
+
+	/* Check is high speed possible and fall back to fast mode if not */
+	if ((dev->master_cfg & DWC_IC_CTRL_SPEED_MASK) == DWC_IC_CTRL_SPEED_HIGH) {
+		if ((comp_param1 & DWC_IC_COMP_PARAM_1_SPEED_MODE_MASK)
+			!= DWC_IC_COMP_PARAM_1_SPEED_MODE_HIGH) {
+			dev_err(dev->dev, "High Speed not supported!\n");
+			t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ;
+			dev->master_cfg &= ~DWC_IC_CTRL_SPEED_MASK;
+			dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+			dev->hs_hcnt = 0;
+			dev->hs_lcnt = 0;
+
+			/* Replace with I2C_MAX_FAST_MODE_PLUS_FREQ */
+			scl_rising_time = max(scl_rising_time, 120);
+			scl_falling_time = max(scl_falling_time, 120);
+			scl_high_time = 260;	/* tHIGH_min = 260 ns */
+			scl_low_time = 500;	/* tLOW_min = 500 ns */
+
+			dev->scl_hcnt = i2c_dwc_scl_hcnt(ic_clk, scl_high_time,
+							 scl_rising_time, spk_cnt);
+			dev->scl_lcnt = i2c_dwc_scl_lcnt(ic_clk, scl_low_time,
+							 scl_falling_time);
+		} else if (!dev->hs_hcnt || !dev->hs_lcnt) {
+			dev->hs_hcnt = dev->scl_hcnt;
+			dev->hs_lcnt = dev->scl_lcnt;
+		}
+		dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n",
+			dev->hs_hcnt, dev->hs_lcnt);
+	} else {
+		dev_dbg(dev->dev, "HCNT:LCNT: %d:%d\n", dev->scl_hcnt, dev->scl_lcnt);
+	}
+
+	return 0;
+}
+
+int i2c_dw_init(struct dw_i2c_dev *dev)
+{
+	int ret;
+
+	ret = i2c_dw_acquire_lock(dev);
+	if (ret)
+		return ret;
+
+	/* Disable the adapter */
+	__i2c_dw_disable(dev);
+
+	/* Write standard speed timing parameters */
+	regmap_write(dev->map, DWC_IC_SCL_HCNT, dev->scl_hcnt);
+	regmap_write(dev->map, DWC_IC_SCL_LCNT, dev->scl_lcnt);
+
+	/* Write high speed timing parameters if supported */
+	if (dev->hs_hcnt && dev->hs_lcnt) {
+		regmap_write(dev->map, DWC_IC_HS_SCL_HCNT, dev->hs_hcnt);
+		regmap_write(dev->map, DWC_IC_HS_SCL_LCNT, dev->hs_lcnt);
+	}
+
+	/* Write SDA hold time if supported */
+	if (dev->sda_hold_time)
+		regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
+
+	i2c_dwc_configure_fifo_master(dev);
+	i2c_dw_release_lock(dev);
+
+	return 0;
+}
+
+static int i2c_dwc_set_sda_hold_time(struct dw_i2c_dev *dev)
+{
+	int ret = regmap_write_bits(dev->map,
+			DW_IC_SDA_HOLD,
+			DW_IC_SDA_HOLD_TX_MASK,
+			dev->sda_hold_time);
+	return ret;
+}
+
+void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
+{
+	struct i2c_msg *msgs = dev->msgs;
+	u32 ic_ctrl = 0, ic_tar = 0;
+	unsigned int dummy;
+
+	/* Disable the adapter */
+	__i2c_dw_disable(dev);
+
+	/* If the slave address is ten bit address, enable 10BITADDR */
+	if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) {
+		ic_ctrl = DWC_IC_CTRL_10BITADDR_CTRLR;
+		/*
+		 * If I2C_DYNAMIC_TAR_UPDATE is set, the 10-bit addressing
+		 * mode has to be enabled via bit 12 of IC_TAR register.
+		 * We set it always as I2C_DYNAMIC_TAR_UPDATE can't be
+		 * detected from registers.
+		 */
+		ic_tar = DW_IC_TAR_10BITADDR_MASTER;
+	}
+
+	/* From i2c-core-smbus.c, I2C_SMBUS_QUICK intentionally has msg[0].len = 0 */
+	if (dev->msgs[0].len == 0)
+		ic_tar |= DWC_IC_TAR_SMBUS_QUICK_CMD | DWC_IC_TAR_SPECIAL;
+
+	regmap_update_bits(dev->map, DWC_IC_CTRL, DWC_IC_CTRL_10BITADDR_CTRLR,
+			   ic_ctrl);
+
+	/*
+	 * Set the slave (target) address and enable 10-bit addressing mode
+	 * if applicable.
+	 */
+	regmap_write(dev->map, DW_IC_TAR,
+		     msgs[dev->msg_write_idx].addr | ic_tar);
+
+	/* Enforce disabled interrupts (due to HW issues) */
+	regmap_write(dev->map, DW_IC_INTR_MASK, 0);
+
+	/* Enable the adapter */
+	__i2c_dw_enable(dev);
+
+	/* Dummy read to avoid the register getting stuck on Bay Trail */
+	regmap_read(dev->map, DW_IC_ENABLE_STATUS, &dummy);
+
+	/* Clear and enable interrupts */
+	regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_CLR_INTR);
+	regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_MASTER_MASK);
+}
+
+void i2c_dw_read_clear_intrbits_common(struct dw_i2c_dev *dev)
+{
+	unsigned int stat;
+
+	/*
+	 * The IC_INTR_STAT register just indicates "enabled" interrupts.
+	 * The unmasked raw version of interrupt status bits is available
+	 * in the IC_RAW_INTR_STAT register.
+	 *
+	 * That is,
+	 *   stat = readl(IC_INTR_STAT);
+	 * equals to,
+	 *   stat = readl(IC_RAW_INTR_STAT) & readl(IC_INTR_MASK);
+	 *
+	 * The raw version might be useful for debugging purposes.
+	 */
+	regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
+	/*
+	 * Do not use the IC_CLR_INTR register to clear interrupts, or
+	 * you'll miss some interrupts, triggered during the period from
+	 * readl(IC_INTR_STAT) to readl(IC_CLR_INTR).
+	 *
+	 * Instead, use the separately-prepared IC_CLR_* registers.
+	 */
+	if (stat & DW_IC_INTR_RX_UNDER)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RX_UNDER);
+	if (stat & DW_IC_INTR_RX_OVER)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RX_OVER);
+	if (stat & DW_IC_INTR_TX_OVER)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_TX_OVER);
+	if (stat & DW_IC_INTR_RD_REQ)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RD_REQ);
+	if (stat & DW_IC_INTR_RX_DONE)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RX_DONE);
+	if (stat & DW_IC_INTR_ACTIVITY)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_ACTIVITY);
+	if (stat & DW_IC_INTR_START_DET)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_START_DET);
+	if (stat & DW_IC_INTR_GEN_CALL)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_GEN_CALL);
+}
+EXPORT_SYMBOL_GPL(i2c_dw_read_clear_intrbits_common);
+
+u32 i2c_dw_read_clear_intrbits(struct dw_i2c_dev *dev)
+{
+	unsigned int stat;
+
+	/*
+	 * The IC_INTR_STAT register just indicates "enabled" interrupts.
+	 * The unmasked raw version of interrupt status bits is available
+	 * in the IC_RAW_INTR_STAT register.
+	 *
+	 * That is,
+	 *   stat = readl(IC_INTR_STAT);
+	 * equals to,
+	 *   stat = readl(IC_RAW_INTR_STAT) & readl(IC_INTR_MASK);
+	 *
+	 * The raw version might be useful for debugging purposes.
+	 */
+	regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
+
+	if (stat & DW_IC_INTR_TX_ABRT) {
+		/*
+		 * The IC_TX_ABRT_SOURCE register is cleared whenever
+		 * the IC_CLR_TX_ABRT is read.  Preserve it beforehand.
+		 */
+		regmap_read(dev->map, DWC_IC_TX_TRMNT_SOURCE, &dev->abort_source);
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_TX_ABRT);
+	}
+	if ((stat & DW_IC_INTR_STOP_DET) &&
+	    (dev->rx_outstanding == 0 || (stat & DW_IC_INTR_RX_FULL)))
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_STOP_DET);
+	if (stat & DW_IC_INTR_RESTART_DET)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RESTART_DET);
+	if (stat & DWC_IC_INTR_SCL_STUCK_AT_LOW)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_SCL_STUCK_DET);
+
+	i2c_dw_read_clear_intrbits_common(dev);
+
+	return stat;
+}
+
+void i2c_dw_configure_master(struct dw_i2c_dev *dev)
+{
+	struct i2c_timings *t = &dev->timings;
+
+	dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY |
+		I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_PEC;
+
+	dev->master_cfg = DWC_IC_CTRL_OP_MODE;
+
+	dev->mode = DW_IC_MASTER;
+
+	switch (t->bus_freq_hz) {
+	case I2C_MAX_STANDARD_MODE_FREQ:
+		dev->master_cfg |= DWC_IC_CTRL_SPEED_STD;
+		break;
+	case I2C_MAX_FAST_MODE_FREQ:
+		dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+		break;
+	case I2C_MAX_FAST_MODE_PLUS_FREQ:
+		dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+		break;
+	case I2C_MAX_HIGH_SPEED_MODE_FREQ:
+		dev->master_cfg |= DWC_IC_CTRL_SPEED_HIGH;
+		break;
+	default:
+		dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+	}
+
+	dev->set_sda_hold_time = i2c_dwc_set_sda_hold_time;
+}
+
+int i2c_dw_probe_master(struct dw_i2c_dev *dev)
+{
+	struct i2c_adapter *adap = &dev->adapter;
+	unsigned int ic_ctrl;
+	int ret;
+
+	init_completion(&dev->cmd_complete);
+
+	ret = i2c_dwc_set_timings_master(dev);
+	if (ret)
+		return ret;
+
+	/* Lock the bus for accessing DWC_IC_CTRL */
+	ret = i2c_dw_acquire_lock(dev);
+	if (ret)
+		return ret;
+
+	/*
+	 * On AMD platforms BIOS advertises the bus clear feature
+	 * and enables the SCL/SDA stuck low. SMU FW does the
+	 * bus recovery process. Driver should not ignore this BIOS
+	 * advertisement of bus clear feature.
+	 */
+	ret = regmap_read(dev->map, DWC_IC_CTRL, &ic_ctrl);
+	i2c_dw_release_lock(dev);
+	if (ret)
+		return ret;
+
+	if (ic_ctrl & DWC_IC_CTRL_BUS_CLEAR_CTRL)
+		dev->master_cfg |= DWC_IC_CTRL_BUS_CLEAR_CTRL;
+
+	ret = i2c_dw_init(dev);
+	if (ret)
+		return ret;
+
+	snprintf(adap->name, sizeof(adap->name),
+		 "Synopsys DWC I2C adapter");
+
+	ret = i2c_dw_init_recovery_info(dev);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+MODULE_AUTHOR("Sankarshan shukla <sashukla@synopsys.com>");
+MODULE_DESCRIPTION("Synopsys DWC I2C bus master adapter");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/i2c-dwc-slave.c b/drivers/i2c/busses/i2c-dwc-slave.c
new file mode 100644
index 000000000000..5369eb881238
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-slave.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DWC I2C adapter driver (slave only).
+ *
+ * Based on the Synopsys DWC I2C adapter driver (master).
+ *
+ * Copyright (C) 2016 Synopsys Inc.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "i2c-designware-core.h"
+
+void i2c_dw_set_mode(struct dw_i2c_dev *dev, int mode)
+{
+	if (dev->mode == DW_IC_SLAVE && mode == DW_IC_SLAVE) {
+		regmap_write(dev->map, DWC_IC_DAR, dev->slave->addr);
+		__i2c_dw_enable(dev);
+		dev->status = 0;
+	}
+}
+
+static void i2c_dwc_configure_fifo_slave(struct dw_i2c_dev *dev)
+{
+	/* Configure the I2C slave. */
+	regmap_write(dev->map, DWC_IC_CTRL, 0);
+	regmap_write(dev->map, DWC_IC_CTRL, dev->slave_cfg);
+	regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_SLAVE_MASK);
+}
+
+/**
+ * i2c_dwc_init_slave() - Initialize the DWC i2c slave hardware
+ * @dev: device private data
+ *
+ * This function configures and enables the I2C in slave mode.
+ * This function is called during I2C init function, and in case of timeout at
+ * run time.
+ */
+static int i2c_dwc_init_slave(struct dw_i2c_dev *dev)
+{
+	int ret;
+	int r_value;
+
+	ret = i2c_dw_acquire_lock(dev);
+	if (ret)
+		return ret;
+
+	/* Disable the adapter. */
+	__i2c_dw_disable(dev);
+
+	/* Write SDA hold time if supported */
+	if (dev->sda_hold_time)
+		regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
+
+	regmap_read(dev->map, DWC_IC_CAPABILITIES, &r_value);
+	if (r_value & DWC_IC_CAPABILITIES_IC_SMBUS) {
+		regmap_read(dev->map, DWC_IC_SMBUS_CAPABILITIES, &r_value);
+		if (r_value & DWC_IC_SMBUS_CAPABILITIES_SMBUS_ARP) {
+			regmap_write(dev->map, DW_IC_ENABLE, DWC_IC_ENABLE_DAR_EN);
+			regmap_write(dev->map, DWC_IC_SMBUS_ARP_CTRL,
+				     DWC_IC_SMBUS_ARP_CTRL_NARP_DEVICE_TYPE);
+		}
+	}
+
+	i2c_dwc_configure_fifo_slave(dev);
+	i2c_dw_release_lock(dev);
+
+	return 0;
+}
+
+u32 i2c_dw_read_clear_intrbits_slave(struct dw_i2c_dev *dev)
+{
+	unsigned int stat;
+	unsigned int smbus_stat;
+
+	/*
+	 * The IC_INTR_STAT register just indicates "enabled" interrupts.
+	 * The unmasked raw version of interrupt status bits is available
+	 * in the IC_RAW_INTR_STAT register.
+	 *
+	 * That is,
+	 *   stat = readl(IC_INTR_STAT);
+	 * equals to,
+	 *   stat = readl(IC_RAW_INTR_STAT) & readl(IC_INTR_MASK);
+	 *
+	 * The raw version might be useful for debugging purposes.
+	 */
+	regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
+	regmap_read(dev->map, DWC_IC_SMBUS_INTR_STAT, &smbus_stat);
+
+	/*
+	 * Do not use the IC_CLR_INTR register to clear interrupts, or
+	 * you'll miss some interrupts, triggered during the period from
+	 * readl(IC_INTR_STAT) to readl(IC_CLR_INTR).
+	 *
+	 * Instead, use the separately-prepared IC_CLR_* registers.
+	 */
+
+	if (stat & DW_IC_INTR_TX_ABRT)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_TX_ABRT);
+	if (stat & DW_IC_INTR_STOP_DET)
+		regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_STOP_DET);
+	if (smbus_stat & DWC_R_SMBUS_ALERT_DET)
+		regmap_write(dev->map, DWC_IC_SMBUS_INTR_CLR, DWC_CLR_SMBUS_ALERT_DET);
+
+	i2c_dw_read_clear_intrbits_common(dev);
+
+	return stat;
+}
+
+#if IS_ENABLED(CONFIG_I2C_DWC_SLAVE)
+static int i2c_dwc_xfer_ms(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+	if (!dev->ms_adapter)
+		return -ENXIO;
+
+	return i2c_dw_xfer(dev->ms_adapter, msgs, num);
+}
+#endif
+
+const struct i2c_algorithm i2c_dw_slave_algo = {
+	.master_xfer = i2c_dwc_xfer_ms,
+	.functionality = i2c_dw_func,
+	.reg_slave = i2c_dw_reg_slave,
+	.unreg_slave = i2c_dw_unreg_slave,
+};
+
+void i2c_dw_configure_slave(struct dw_i2c_dev *dev)
+{
+	dev->functionality = I2C_FUNC_SLAVE | DW_IC_DEFAULT_FUNCTIONALITY;
+
+	dev->slave_cfg = DWC_IC_CTRL_RX_FIFO_FULL_HLD_CTRL |
+			 DWC_IC_CTRL_STOP_DET_IFADDRESSED;
+
+	dev->mode = DW_IC_SLAVE;
+}
+
+int i2c_dw_probe_slave(struct dw_i2c_dev *dev)
+{
+	struct i2c_adapter *adap = &dev->adapter;
+	struct device_node *ms_node;
+	int ret;
+
+	ret = i2c_dwc_init_slave(dev);
+	if (ret)
+		return ret;
+
+	snprintf(adap->name, sizeof(adap->name), "Synopsys DWC I2C Slave adapter");
+
+	/* Get the specified I2C master adapter from DTS for MCTP */
+	ms_node = of_parse_phandle(dev->dev->of_node, "starfive,mctp-i2c-ms", 0);
+	if (ms_node) {
+		struct platform_device *ms_pdev = of_find_device_by_node(ms_node);
+
+		if (ms_pdev) {
+			struct dw_i2c_dev *ms_dev = platform_get_drvdata(ms_pdev);
+
+			if (ms_dev)
+				dev->ms_adapter = &ms_dev->adapter;
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(i2c_dw_probe_slave);
+
+MODULE_AUTHOR("Sankarshan shukla <sashukla@synopsys.com>");
+MODULE_DESCRIPTION("Synopsys DWC I2C bus slave adapter");
+MODULE_LICENSE("GPL");
-- 
2.43.0


  parent reply	other threads:[~2026-05-21  4:00 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-21  3:43 [PATCH v1 0/3] i2c: dwc: Add I2C DWC master/slave support for StarFive JHB100 lianfeng.ouyang
2026-05-21  3:43 ` [PATCH v1 1/3] dt-bindings: i2c: snps,dwc-i2c: Add StarFive JHB100 bindings lianfeng.ouyang
2026-05-21 20:22   ` Conor Dooley
2026-05-26  6:48     ` 回复: " Lianfeng Ouyang
2026-05-21 20:34   ` Krzysztof Kozlowski
2026-05-26 10:07     ` 回复: " Lianfeng Ouyang
2026-05-21  3:43 ` [PATCH v1 2/3] i2c: designware: Export symbols and add __weak for DWC I2C driver lianfeng.ouyang
2026-05-21  3:43 ` lianfeng.ouyang [this message]
2026-05-21  4:55 ` [PATCH v1 0/3] i2c: dwc: Add I2C DWC master/slave support for StarFive JHB100 Mika Westerberg
2026-05-21  7:01   ` 回复: " Lianfeng Ouyang
2026-05-21  8:47     ` Mika Westerberg

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260521034340.27837-4-lianfeng.ouyang@starfivetech.com \
    --to=lianfeng.ouyang@starfivetech.com \
    --cc=andi.shyti@kernel.org \
    --cc=andriy.shevchenko@linux.intel.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=jsd@semihalf.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mika.westerberg@linux.intel.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®