From: Inochi Amaoto <inochiama@gmail.com>
To: Srinivas Kandagatla <srini@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Chen Wang <chen.wang@linux.dev>,
Inochi Amaoto <inochiama@gmail.com>,
Paul Walmsley <pjw@kernel.org>, Albert Ou <aou@eecs.berkeley.edu>,
Alexandre Ghiti <alex@ghiti.fr>
Cc: devicetree@vger.kernel.org, sophgo@lists.linux.dev,
linux-kernel@vger.kernel.org, linux-riscv@lists.infradead.org,
Yixun Lan <dlan@gentoo.org>, Longbin Li <looong.bin@gmail.com>
Subject: [PATCH 2/3] nvmem: Add Sophgo SG2044 eFuse driver
Date: Wed, 14 Oct 2026 15:45:14 +0800 [thread overview]
Message-ID: <20261014074516.240247-3-inochiama@gmail.com> (raw)
In-Reply-To: <20261014074516.240247-1-inochiama@gmail.com>
Sophgo SoCs such as SG2044 contain eFuses used to store
factory-programmed data.
As for SG2044, HW automatically loads the eFuse content
into shadow registers which are organized as 32bit values
exposed as MMIO.
Signed-off-by: Inochi Amaoto <inochiama@gmail.com>
---
drivers/nvmem/Kconfig | 12 +++
drivers/nvmem/Makefile | 2 +
drivers/nvmem/sophgo-efuse.c | 176 +++++++++++++++++++++++++++++++++++
3 files changed, 190 insertions(+)
create mode 100644 drivers/nvmem/sophgo-efuse.c
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index c36c2a4c2a0b..a7936dd65bdd 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -465,6 +465,18 @@ config NVMEM_SNVS_LPGPR
This driver can also be built as a module. If so, the module
will be called nvmem-snvs-lpgpr.
+config NVMEM_SOPHGO_EFUSE
+ tristate "Sophgo eFuse support"
+ depends on ARCH_SOPHGO || COMPILE_TEST
+ default ARCH_SOPHGO
+ help
+ Say y here to enable support for reading eFuses on Sophgo SoCs
+ such as the CV1800B. These are e.g. used to store factory programmed
+ calibration data required for the builtin ethernet PHY.
+
+ This driver can also be built as a module. If so, the module will
+ be called nvmem-sophgo-efuse.
+
config NVMEM_SPMI_SDAM
tristate "SPMI SDAM Support"
depends on SPMI
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 2bbfb9ff1885..430ddddf8c3c 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -79,6 +79,8 @@ obj-$(CONFIG_NVMEM_SC27XX_EFUSE) += nvmem-sc27xx-efuse.o
nvmem-sc27xx-efuse-y := sc27xx-efuse.o
obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o
nvmem_snvs_lpgpr-y := snvs_lpgpr.o
+obj-$(CONFIG_NVMEM_SOPHGO_EFUSE) += nvmem-sophgo-efuse.o
+nvmem-sophgo-efuse-y := sophgo-efuse.o
obj-$(CONFIG_NVMEM_SPMI_SDAM) += nvmem_qcom-spmi-sdam.o
nvmem_qcom-spmi-sdam-y += qcom-spmi-sdam.o
obj-$(CONFIG_NVMEM_SPRD_EFUSE) += nvmem_sprd_efuse.o
diff --git a/drivers/nvmem/sophgo-efuse.c b/drivers/nvmem/sophgo-efuse.c
new file mode 100644
index 000000000000..5f90adaf8e4f
--- /dev/null
+++ b/drivers/nvmem/sophgo-efuse.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sophgo SoC eFuse driver
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/cleanup.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-provider.h>
+#include <linux/platform_device.h>
+
+#define SG2044_EFUSE_CONTENT_SIZE 0x400
+
+#define SG2044_EFUSE_MD 0x000
+#define SG2044_EFUSE_ADR 0x004
+#define SG2044_EFUSE_RD_DATA 0x00c
+
+#define SG2044_EFUSE_MODE GENMASK(1, 0)
+#define SG2044_EFUSE_MODE_READ 2
+
+#define SG2044_EFUSE_BOOT_DONE BIT(7)
+#define SG2044_BOOT_TIMEOUT 10000
+
+#define SG2044_EFUSE_ADR_ADDR GENMASK(7, 0)
+
+#define SG2044_EFUSE_ALIGN 4
+
+struct sophgo_efuses {
+ void __iomem *base;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct mutex mutex;
+};
+
+static int sg2044_efuse_wait_mode(struct sophgo_efuses *efuse)
+{
+ u32 value;
+
+ return readl_poll_timeout(efuse->base + SG2044_EFUSE_MD, value,
+ FIELD_GET(SG2044_EFUSE_MODE, value) == 0,
+ 1, SG2044_BOOT_TIMEOUT);
+}
+
+static int sg2044_efuse_set_mode(struct sophgo_efuses *efuse, int mode)
+{
+ u32 val = readl(efuse->base + SG2044_EFUSE_MD);
+
+ val &= ~SG2044_EFUSE_MODE;
+ val |= FIELD_PREP(SG2044_EFUSE_MODE, mode);
+
+ writel(val, efuse->base + SG2044_EFUSE_MD);
+
+ return sg2044_efuse_wait_mode(efuse);
+}
+
+static u32 sg2044_efuses_read_strip(struct sophgo_efuses *efuse,
+ unsigned int offset, u32 *strip)
+{
+ u32 val = FIELD_PREP(SG2044_EFUSE_ADR_ADDR, offset);
+ int ret;
+
+ guard(mutex)(&efuse->mutex);
+
+ writel(val, efuse->base + SG2044_EFUSE_ADR);
+
+ ret = sg2044_efuse_set_mode(efuse, SG2044_EFUSE_MODE_READ);
+ if (ret < 0)
+ return ret;
+
+ *strip = readl(efuse->base + SG2044_EFUSE_RD_DATA);
+
+ return 0;
+}
+
+static int sg2044_efuses_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct sophgo_efuses *efuse = context;
+ unsigned int start, start_offset, end, i;
+ u32 value;
+ u8 *buf;
+ int ret;
+
+ start = rounddown(offset, SG2044_EFUSE_ALIGN);
+ end = roundup(offset + bytes, SG2044_EFUSE_ALIGN);
+ start_offset = offset - start;
+
+ start /= SG2044_EFUSE_ALIGN;
+ end /= SG2044_EFUSE_ALIGN;
+
+ ret = readl_poll_timeout(efuse->base + SG2044_EFUSE_MD, value,
+ (value & SG2044_EFUSE_BOOT_DONE),
+ 1, SG2044_BOOT_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ buf = kzalloc(end - start, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = start; i < end; i++) {
+ ret = sg2044_efuses_read_strip(efuse, i, &value);
+ if (ret)
+ goto failed;
+
+ memcpy(&buf[(i - start) * 4], &value, SG2044_EFUSE_ALIGN);
+ }
+
+ memcpy(val, buf + start_offset, bytes);
+
+failed:
+ kfree(buf);
+
+ return ret;
+}
+
+static int sophgo_efuses_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sophgo_efuses *efuse;
+ struct nvmem_config config = {
+ .dev = &pdev->dev,
+ .add_legacy_fixed_of_cells = true,
+ .read_only = true,
+ .reg_read = sg2044_efuses_read,
+ .stride = 1,
+ .word_size = 1,
+ .name = "sophgo-efuse",
+ .id = NVMEM_DEVID_AUTO,
+ .root_only = true,
+ };
+
+ efuse = devm_kzalloc(dev, sizeof(*efuse), GFP_KERNEL);
+ if (!efuse)
+ return -ENOMEM;
+
+ efuse->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(efuse->base))
+ return PTR_ERR(efuse->base);
+
+ efuse->num_clks = devm_clk_bulk_get_all_enabled(&pdev->dev, &efuse->clks);
+ if (efuse->num_clks < 0)
+ return dev_err_probe(dev, efuse->num_clks, "failed to get clocks\n");
+
+ config.priv = efuse;
+ config.size = SG2044_EFUSE_CONTENT_SIZE;
+
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
+}
+
+static const struct of_device_id sophgo_efuses_of_match[] = {
+ { .compatible = "sophgo,sg2044-efuse", },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, sophgo_efuses_of_match);
+
+static struct platform_driver sophgo_efuses_driver = {
+ .driver = {
+ .name = "sophgo_efuse",
+ .of_match_table = sophgo_efuses_of_match,
+ },
+ .probe = sophgo_efuses_probe,
+};
+
+module_platform_driver(sophgo_efuses_driver);
+
+MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>");
+MODULE_DESCRIPTION("Sophgo efuse driver");
+MODULE_LICENSE("GPL");
--
2.56.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
WARNING: multiple messages have this Message-ID (diff)
From: Inochi Amaoto <inochiama@gmail.com>
To: Srinivas Kandagatla <srini@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Chen Wang <chen.wang@linux.dev>,
Inochi Amaoto <inochiama@gmail.com>,
Paul Walmsley <pjw@kernel.org>, Albert Ou <aou@eecs.berkeley.edu>,
Alexandre Ghiti <alex@ghiti.fr>
Cc: devicetree@vger.kernel.org, sophgo@lists.linux.dev,
linux-kernel@vger.kernel.org, linux-riscv@lists.infradead.org,
Yixun Lan <dlan@gentoo.org>, Longbin Li <looong.bin@gmail.com>
Subject: [PATCH 2/3] nvmem: Add Sophgo SG2044 eFuse driver
Date: Wed, 14 Oct 2026 15:45:14 +0800 [thread overview]
Message-ID: <20261014074516.240247-3-inochiama@gmail.com> (raw)
In-Reply-To: <20261014074516.240247-1-inochiama@gmail.com>
Sophgo SoCs such as SG2044 contain eFuses used to store
factory-programmed data.
As for SG2044, HW automatically loads the eFuse content
into shadow registers which are organized as 32bit values
exposed as MMIO.
Signed-off-by: Inochi Amaoto <inochiama@gmail.com>
---
drivers/nvmem/Kconfig | 12 +++
drivers/nvmem/Makefile | 2 +
drivers/nvmem/sophgo-efuse.c | 176 +++++++++++++++++++++++++++++++++++
3 files changed, 190 insertions(+)
create mode 100644 drivers/nvmem/sophgo-efuse.c
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index c36c2a4c2a0b..a7936dd65bdd 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -465,6 +465,18 @@ config NVMEM_SNVS_LPGPR
This driver can also be built as a module. If so, the module
will be called nvmem-snvs-lpgpr.
+config NVMEM_SOPHGO_EFUSE
+ tristate "Sophgo eFuse support"
+ depends on ARCH_SOPHGO || COMPILE_TEST
+ default ARCH_SOPHGO
+ help
+ Say y here to enable support for reading eFuses on Sophgo SoCs
+ such as the CV1800B. These are e.g. used to store factory programmed
+ calibration data required for the builtin ethernet PHY.
+
+ This driver can also be built as a module. If so, the module will
+ be called nvmem-sophgo-efuse.
+
config NVMEM_SPMI_SDAM
tristate "SPMI SDAM Support"
depends on SPMI
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 2bbfb9ff1885..430ddddf8c3c 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -79,6 +79,8 @@ obj-$(CONFIG_NVMEM_SC27XX_EFUSE) += nvmem-sc27xx-efuse.o
nvmem-sc27xx-efuse-y := sc27xx-efuse.o
obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o
nvmem_snvs_lpgpr-y := snvs_lpgpr.o
+obj-$(CONFIG_NVMEM_SOPHGO_EFUSE) += nvmem-sophgo-efuse.o
+nvmem-sophgo-efuse-y := sophgo-efuse.o
obj-$(CONFIG_NVMEM_SPMI_SDAM) += nvmem_qcom-spmi-sdam.o
nvmem_qcom-spmi-sdam-y += qcom-spmi-sdam.o
obj-$(CONFIG_NVMEM_SPRD_EFUSE) += nvmem_sprd_efuse.o
diff --git a/drivers/nvmem/sophgo-efuse.c b/drivers/nvmem/sophgo-efuse.c
new file mode 100644
index 000000000000..5f90adaf8e4f
--- /dev/null
+++ b/drivers/nvmem/sophgo-efuse.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sophgo SoC eFuse driver
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/cleanup.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-provider.h>
+#include <linux/platform_device.h>
+
+#define SG2044_EFUSE_CONTENT_SIZE 0x400
+
+#define SG2044_EFUSE_MD 0x000
+#define SG2044_EFUSE_ADR 0x004
+#define SG2044_EFUSE_RD_DATA 0x00c
+
+#define SG2044_EFUSE_MODE GENMASK(1, 0)
+#define SG2044_EFUSE_MODE_READ 2
+
+#define SG2044_EFUSE_BOOT_DONE BIT(7)
+#define SG2044_BOOT_TIMEOUT 10000
+
+#define SG2044_EFUSE_ADR_ADDR GENMASK(7, 0)
+
+#define SG2044_EFUSE_ALIGN 4
+
+struct sophgo_efuses {
+ void __iomem *base;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct mutex mutex;
+};
+
+static int sg2044_efuse_wait_mode(struct sophgo_efuses *efuse)
+{
+ u32 value;
+
+ return readl_poll_timeout(efuse->base + SG2044_EFUSE_MD, value,
+ FIELD_GET(SG2044_EFUSE_MODE, value) == 0,
+ 1, SG2044_BOOT_TIMEOUT);
+}
+
+static int sg2044_efuse_set_mode(struct sophgo_efuses *efuse, int mode)
+{
+ u32 val = readl(efuse->base + SG2044_EFUSE_MD);
+
+ val &= ~SG2044_EFUSE_MODE;
+ val |= FIELD_PREP(SG2044_EFUSE_MODE, mode);
+
+ writel(val, efuse->base + SG2044_EFUSE_MD);
+
+ return sg2044_efuse_wait_mode(efuse);
+}
+
+static u32 sg2044_efuses_read_strip(struct sophgo_efuses *efuse,
+ unsigned int offset, u32 *strip)
+{
+ u32 val = FIELD_PREP(SG2044_EFUSE_ADR_ADDR, offset);
+ int ret;
+
+ guard(mutex)(&efuse->mutex);
+
+ writel(val, efuse->base + SG2044_EFUSE_ADR);
+
+ ret = sg2044_efuse_set_mode(efuse, SG2044_EFUSE_MODE_READ);
+ if (ret < 0)
+ return ret;
+
+ *strip = readl(efuse->base + SG2044_EFUSE_RD_DATA);
+
+ return 0;
+}
+
+static int sg2044_efuses_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct sophgo_efuses *efuse = context;
+ unsigned int start, start_offset, end, i;
+ u32 value;
+ u8 *buf;
+ int ret;
+
+ start = rounddown(offset, SG2044_EFUSE_ALIGN);
+ end = roundup(offset + bytes, SG2044_EFUSE_ALIGN);
+ start_offset = offset - start;
+
+ start /= SG2044_EFUSE_ALIGN;
+ end /= SG2044_EFUSE_ALIGN;
+
+ ret = readl_poll_timeout(efuse->base + SG2044_EFUSE_MD, value,
+ (value & SG2044_EFUSE_BOOT_DONE),
+ 1, SG2044_BOOT_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ buf = kzalloc(end - start, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = start; i < end; i++) {
+ ret = sg2044_efuses_read_strip(efuse, i, &value);
+ if (ret)
+ goto failed;
+
+ memcpy(&buf[(i - start) * 4], &value, SG2044_EFUSE_ALIGN);
+ }
+
+ memcpy(val, buf + start_offset, bytes);
+
+failed:
+ kfree(buf);
+
+ return ret;
+}
+
+static int sophgo_efuses_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sophgo_efuses *efuse;
+ struct nvmem_config config = {
+ .dev = &pdev->dev,
+ .add_legacy_fixed_of_cells = true,
+ .read_only = true,
+ .reg_read = sg2044_efuses_read,
+ .stride = 1,
+ .word_size = 1,
+ .name = "sophgo-efuse",
+ .id = NVMEM_DEVID_AUTO,
+ .root_only = true,
+ };
+
+ efuse = devm_kzalloc(dev, sizeof(*efuse), GFP_KERNEL);
+ if (!efuse)
+ return -ENOMEM;
+
+ efuse->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(efuse->base))
+ return PTR_ERR(efuse->base);
+
+ efuse->num_clks = devm_clk_bulk_get_all_enabled(&pdev->dev, &efuse->clks);
+ if (efuse->num_clks < 0)
+ return dev_err_probe(dev, efuse->num_clks, "failed to get clocks\n");
+
+ config.priv = efuse;
+ config.size = SG2044_EFUSE_CONTENT_SIZE;
+
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
+}
+
+static const struct of_device_id sophgo_efuses_of_match[] = {
+ { .compatible = "sophgo,sg2044-efuse", },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, sophgo_efuses_of_match);
+
+static struct platform_driver sophgo_efuses_driver = {
+ .driver = {
+ .name = "sophgo_efuse",
+ .of_match_table = sophgo_efuses_of_match,
+ },
+ .probe = sophgo_efuses_probe,
+};
+
+module_platform_driver(sophgo_efuses_driver);
+
+MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>");
+MODULE_DESCRIPTION("Sophgo efuse driver");
+MODULE_LICENSE("GPL");
--
2.56.0
next prev parent reply other threads:[~2026-10-14 7:46 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 7:45 [PATCH 0/3] Add SG2044 eFUSE driver Inochi Amaoto
2026-10-14 7:45 ` Inochi Amaoto
2026-10-14 7:45 ` [PATCH 1/3] dt-bindings: nvmem: Add SG2044 eFuse controller Inochi Amaoto
2026-10-14 7:45 ` Inochi Amaoto
2026-10-14 11:43 ` Krzysztof Kozlowski
2026-10-14 11:43 ` Krzysztof Kozlowski
2026-10-14 7:45 ` Inochi Amaoto [this message]
2026-10-14 7:45 ` [PATCH 2/3] nvmem: Add Sophgo SG2044 eFuse driver Inochi Amaoto
2026-10-14 7:53 ` sashiko-bot
2026-10-14 7:45 ` [PATCH 3/3] riscv: dts: sophgo: sg2044: Add eFUSE device Inochi Amaoto
2026-10-14 7:45 ` Inochi Amaoto
2026-10-14 7:54 ` sashiko-bot
2026-10-14 11:43 ` Krzysztof Kozlowski
2026-10-14 11:43 ` Krzysztof Kozlowski
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=20261014074516.240247-3-inochiama@gmail.com \
--to=inochiama@gmail.com \
--cc=alex@ghiti.fr \
--cc=aou@eecs.berkeley.edu \
--cc=chen.wang@linux.dev \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dlan@gentoo.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=looong.bin@gmail.com \
--cc=pjw@kernel.org \
--cc=robh@kernel.org \
--cc=sophgo@lists.linux.dev \
--cc=srini@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.