From: Stefan Kerkmann <s.kerkmann@pengutronix.de>
To: Sascha Hauer <s.hauer@pengutronix.de>,
BAREBOX <barebox@lists.infradead.org>
Cc: Stefan Kerkmann <s.kerkmann@pengutronix.de>
Subject: [PATCH] ARM: i.MX8MP: enhance support for ocotp driver
Date: Tue, 16 Jan 2024 16:00:58 +0100 [thread overview]
Message-ID: <20240116-feature-imx8mp-octop-driver-v1-1-5143a215f657@pengutronix.de> (raw)
The OTP memory address space found in the i.MX8MP extends to 0x17F[1]
instead of the usual 0xFF[2]. Hence, the ADDR field in the HW_OCOTP_CTRL
register was extended to hold 9 bits, shifting all subsequent fields up
by one bit. As the access patterns are otherwise identical we inject the
correct bitmaps for the CTRL register access via the newly introduced
`ocotp_ctrl_reg` and keep the driver generic.
One additional bug were missing `set_timing`, `efuse_blow` and
`efuse_read` function pointers - resulting in a null reference exception
when programming the fuses e.g. burning the SRK hashes for HAB boot. As
the peripheral is identical to the other SOCs found in the i.MX family
the standard implementation was simply added.
[1]: see Figure 6-25 OTP Memory Footprint, i.MX8MP RM Rev. 1
[2]: see Figure 6-28 OTP Memory Footprint, i.MX8MM RM Rev. 3
Signed-off-by: Stefan Kerkmann <s.kerkmann@pengutronix.de>
---
drivers/nvmem/ocotp.c | 143 +++++++++++++++++++++++++++++++++++---------------
1 file changed, 101 insertions(+), 42 deletions(-)
diff --git a/drivers/nvmem/ocotp.c b/drivers/nvmem/ocotp.c
index 6480fd9523..66a88ae480 100644
--- a/drivers/nvmem/ocotp.c
+++ b/drivers/nvmem/ocotp.c
@@ -25,6 +25,7 @@
#include <of.h>
#include <clock.h>
#include <linux/regmap.h>
+#include <linux/bits.h>
#include <linux/clk.h>
#include <machine_id.h>
#ifdef CONFIG_ARCH_IMX
@@ -66,25 +67,29 @@
#define DEF_STROBE_PROG 10000 /* IPG clocks */
/* OCOTP Registers bits and masks */
-#define OCOTP_CTRL_WR_UNLOCK 16
-#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77
-#define OCOTP_CTRL_WR_UNLOCK_MASK 0xFFFF0000
-#define OCOTP_CTRL_ADDR 0
-#define OCOTP_CTRL_ADDR_MASK 0x000000FF
-#define OCOTP_CTRL_BUSY (1 << 8)
-#define OCOTP_CTRL_ERROR (1 << 9)
-#define OCOTP_CTRL_RELOAD_SHADOWS (1 << 10)
-
-#define OCOTP_TIMING_STROBE_READ_MASK 0x003F0000
-#define OCOTP_TIMING_RELAX_MASK 0x0000F000
-#define OCOTP_TIMING_STROBE_PROG_MASK 0x00000FFF
-#define OCOTP_TIMING_WAIT_MASK 0x0FC00000
-
-#define OCOTP_READ_CTRL_READ_FUSE 0x00000001
-
-#define BF(value, field) FIELD_PREP(field##_MASK, value)
-
-#define OCOTP_OFFSET_TO_ADDR(o) (OCOTP_OFFSET_TO_INDEX(o) * 4)
+#define OCOTP_CTRL_ADDR GENMASK(7, 0)
+#define OCOTP_CTRL_BUSY BIT(8)
+#define OCOTP_CTRL_ERROR BIT(9)
+#define OCOTP_CTRL_RELOAD_SHADOWS BIT(10)
+#define OCOTP_CTRL_WR_UNLOCK GENMASK(31, 16)
+#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77
+
+/* i.MX8MP OCOTP CTRL has a different layout. See RM Rev.1 06/2021 Section
+ * 6.3.5.1.2.4 */
+#define OCOTP_CTRL_ADDR_8MP GENMASK(8, 0)
+#define OCOTP_CTRL_BUSY_8MP BIT(9)
+#define OCOTP_CTRL_ERROR_8MP BIT(10)
+#define OCOTP_CTRL_RELOAD_SHADOWS_8MP BIT(11)
+#define OCOTP_CTRL_WR_UNLOCK_8MP GENMASK(31, 16)
+
+#define OCOTP_TIMING_STROBE_READ GENMASK(21, 16)
+#define OCOTP_TIMING_RELAX GENMASK(15, 12)
+#define OCOTP_TIMING_STROBE_PROG GENMASK(11, 0)
+#define OCOTP_TIMING_WAIT GENMASK(27, 22)
+
+#define OCOTP_READ_CTRL_READ_FUSE BIT(1)
+
+#define OCOTP_OFFSET_TO_ADDR(o) (OCOTP_OFFSET_TO_INDEX(o) * 4)
/* Other definitions */
#define IMX6_OTP_DATA_ERROR_VAL 0xBADABADA
@@ -99,11 +104,37 @@
#define MAC_BYTES 8
#define UNIQUE_ID_NUM 2
+#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
+
enum imx_ocotp_format_mac_direction {
OCOTP_HW_TO_MAC,
OCOTP_MAC_TO_HW,
};
+struct ocotp_ctrl_reg {
+ u32 bm_addr;
+ u32 bm_busy;
+ u32 bm_error;
+ u32 bm_reload_shadows;
+ u32 bm_wr_unlock;
+};
+
+const struct ocotp_ctrl_reg ocotp_ctrl_reg_default = {
+ .bm_addr = OCOTP_CTRL_ADDR,
+ .bm_busy = OCOTP_CTRL_BUSY,
+ .bm_error = OCOTP_CTRL_ERROR,
+ .bm_reload_shadows = OCOTP_CTRL_RELOAD_SHADOWS,
+ .bm_wr_unlock = OCOTP_CTRL_WR_UNLOCK,
+};
+
+const struct ocotp_ctrl_reg ocotp_ctrl_reg_8mp = {
+ .bm_addr = OCOTP_CTRL_ADDR_8MP,
+ .bm_busy = OCOTP_CTRL_BUSY_8MP,
+ .bm_error = OCOTP_CTRL_ERROR_8MP,
+ .bm_reload_shadows = OCOTP_CTRL_RELOAD_SHADOWS_8MP,
+ .bm_wr_unlock = OCOTP_CTRL_WR_UNLOCK_8MP,
+};
+
struct ocotp_priv;
struct imx_ocotp_data {
@@ -117,6 +148,7 @@ struct imx_ocotp_data {
u8 mac_offsets[MAX_MAC_OFFSETS];
u8 mac_offsets_num;
struct imx8m_featctrl_data *feat;
+ const struct ocotp_ctrl_reg *ctrl;
};
struct ocotp_priv_ethaddr {
@@ -181,10 +213,10 @@ static int imx6_ocotp_set_timing(struct ocotp_priv *priv)
1000000);
strobe_prog += 2 * (relax + 1) - 1;
- timing = readl(priv->base + OCOTP_TIMING) & OCOTP_TIMING_WAIT_MASK;
- timing |= BF(relax, OCOTP_TIMING_RELAX);
- timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ);
- timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG);
+ timing = readl(priv->base + OCOTP_TIMING) & OCOTP_TIMING_WAIT;
+ timing |= FIELD_PREP(OCOTP_TIMING_RELAX, relax);
+ timing |= FIELD_PREP(OCOTP_TIMING_STROBE_READ, strobe_read);
+ timing |= FIELD_PREP(OCOTP_TIMING_STROBE_PROG, strobe_prog);
writel(timing, priv->base + OCOTP_TIMING);
@@ -215,8 +247,9 @@ static int imx7_ocotp_set_timing(struct ocotp_priv *priv)
static int imx6_ocotp_wait_busy(struct ocotp_priv *priv, u32 flags)
{
uint64_t start = get_time_ns();
+ u32 bm_ctrl_busy = priv->data->ctrl->bm_busy;
- while (readl(priv->base + OCOTP_CTRL) & (OCOTP_CTRL_BUSY | flags))
+ while (readl(priv->base + OCOTP_CTRL) & (bm_ctrl_busy | flags))
if (is_timeout(start, MSECOND))
return -ETIMEDOUT;
@@ -242,13 +275,16 @@ static int imx6_fuse_read_addr(struct ocotp_priv *priv, u32 addr, u32 *pdata)
{
u32 ctrl_reg;
int ret;
+ u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ u32 bm_ctrl_addr = priv->data->ctrl->bm_addr;
+ u32 bm_ctrl_wr_unlock = priv->data->ctrl->bm_wr_unlock;
- writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
ctrl_reg = readl(priv->base + OCOTP_CTRL);
- ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
- ctrl_reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK;
- ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+ ctrl_reg &= ~bm_ctrl_addr;
+ ctrl_reg &= ~bm_ctrl_wr_unlock;
+ ctrl_reg |= field_prep(bm_ctrl_addr, addr);
writel(ctrl_reg, priv->base + OCOTP_CTRL);
writel(OCOTP_READ_CTRL_READ_FUSE, priv->base + OCOTP_READ_CTRL);
@@ -256,7 +292,7 @@ static int imx6_fuse_read_addr(struct ocotp_priv *priv, u32 addr, u32 *pdata)
if (ret)
return ret;
- if (readl(priv->base + OCOTP_CTRL) & OCOTP_CTRL_ERROR)
+ if (readl(priv->base + OCOTP_CTRL) & bm_ctrl_error)
*pdata = 0xbadabada;
else
*pdata = readl(priv->base + OCOTP_READ_FUSE_DATA);
@@ -270,16 +306,19 @@ static int imx7_fuse_read_addr(struct ocotp_priv *priv, u32 index, u32 *pdata)
u32 bank_addr;
u16 word;
int ret;
+ u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ u32 bm_ctrl_addr = priv->data->ctrl->bm_addr;
+ u32 bm_ctrl_wr_unlock = priv->data->ctrl->bm_wr_unlock;
word = index & 0x3;
bank_addr = index >> 2;
- writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
ctrl_reg = readl(priv->base + OCOTP_CTRL);
- ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
- ctrl_reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK;
- ctrl_reg |= BF(bank_addr, OCOTP_CTRL_ADDR);
+ ctrl_reg &= ~bm_ctrl_addr;
+ ctrl_reg &= ~bm_ctrl_wr_unlock;
+ ctrl_reg |= field_prep(bm_ctrl_addr, bank_addr);
writel(ctrl_reg, priv->base + OCOTP_CTRL);
writel(OCOTP_READ_CTRL_READ_FUSE, priv->base + MX7_OCOTP_READ_CTRL);
@@ -287,7 +326,7 @@ static int imx7_fuse_read_addr(struct ocotp_priv *priv, u32 index, u32 *pdata)
if (ret)
return ret;
- if (readl(priv->base + OCOTP_CTRL) & OCOTP_CTRL_ERROR)
+ if (readl(priv->base + OCOTP_CTRL) & bm_ctrl_error)
*pdata = 0xbadabada;
else
switch (word) {
@@ -351,24 +390,28 @@ static int imx_ocotp_reg_read(void *ctx, unsigned int reg, unsigned int *val)
static void imx_ocotp_clear_unlock(struct ocotp_priv *priv, u32 index)
{
u32 ctrl_reg;
+ u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ u32 bm_ctrl_addr = priv->data->ctrl->bm_addr;
+ u32 bm_ctrl_wr_unlock = priv->data->ctrl->bm_wr_unlock;
- writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
/* Control register */
ctrl_reg = readl(priv->base + OCOTP_CTRL);
- ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
- ctrl_reg |= BF(index, OCOTP_CTRL_ADDR);
- ctrl_reg |= BF(OCOTP_CTRL_WR_UNLOCK_KEY, OCOTP_CTRL_WR_UNLOCK);
+ ctrl_reg &= ~bm_ctrl_addr;
+ ctrl_reg |= field_prep(bm_ctrl_addr, index);
+ ctrl_reg |= field_prep(bm_ctrl_wr_unlock, OCOTP_CTRL_WR_UNLOCK_KEY);
writel(ctrl_reg, priv->base + OCOTP_CTRL);
}
static int imx6_fuse_blow_addr(struct ocotp_priv *priv, u32 index, u32 value)
{
int ret;
+ u32 bm_ctrl_error = priv->data->ctrl->bm_error;
imx_ocotp_clear_unlock(priv, index);
- writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
writel(value, priv->base + OCOTP_DATA);
ret = imx6_ocotp_wait_busy(priv, 0);
@@ -429,17 +472,20 @@ static int imx7_fuse_blow_addr(struct ocotp_priv *priv, u32 index, u32 value)
static int imx6_ocotp_reload_shadow(struct ocotp_priv *priv)
{
+ u32 bm_ctrl_reload_shadows = priv->data->ctrl->bm_reload_shadows;
+
dev_info(&priv->dev, "reloading shadow registers...\n");
- writel(OCOTP_CTRL_RELOAD_SHADOWS, priv->base + OCOTP_CTRL_SET);
+ writel(bm_ctrl_reload_shadows, priv->base + OCOTP_CTRL_SET);
udelay(1);
- return imx6_ocotp_wait_busy(priv, OCOTP_CTRL_RELOAD_SHADOWS);
+ return imx6_ocotp_wait_busy(priv, bm_ctrl_reload_shadows);
}
static int imx6_ocotp_blow_one_u32(struct ocotp_priv *priv, u32 index, u32 data,
u32 *pfused_value)
{
int ret;
+ u32 bm_ctrl_error = priv->data->ctrl->bm_error;
ret = imx6_ocotp_prepare(priv);
if (ret) {
@@ -453,7 +499,7 @@ static int imx6_ocotp_blow_one_u32(struct ocotp_priv *priv, u32 index, u32 data,
return ret;
}
- if (readl(priv->base + OCOTP_CTRL) & OCOTP_CTRL_ERROR) {
+ if (readl(priv->base + OCOTP_CTRL) & bm_ctrl_error) {
dev_err(&priv->dev, "bad write status\n");
return -EFAULT;
}
@@ -861,6 +907,7 @@ static struct imx_ocotp_data imx6q_ocotp_data = {
.set_timing = imx6_ocotp_set_timing,
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx6sl_ocotp_data = {
@@ -872,6 +919,7 @@ static struct imx_ocotp_data imx6sl_ocotp_data = {
.set_timing = imx6_ocotp_set_timing,
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx6ul_ocotp_data = {
@@ -883,6 +931,7 @@ static struct imx_ocotp_data imx6ul_ocotp_data = {
.set_timing = imx6_ocotp_set_timing,
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx6ull_ocotp_data = {
@@ -894,6 +943,7 @@ static struct imx_ocotp_data imx6ull_ocotp_data = {
.set_timing = imx6_ocotp_set_timing,
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data vf610_ocotp_data = {
@@ -905,6 +955,7 @@ static struct imx_ocotp_data vf610_ocotp_data = {
.set_timing = imx6_ocotp_set_timing,
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx8m_featctrl_data imx8mp_featctrl_data = {
@@ -920,6 +971,10 @@ static struct imx_ocotp_data imx8mp_ocotp_data = {
.mac_offsets = { 0x90, 0x94 },
.format_mac = imx_ocotp_format_mac,
.feat = &imx8mp_featctrl_data,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_8mp,
};
static struct imx_ocotp_data imx8mq_ocotp_data = {
@@ -931,6 +986,7 @@ static struct imx_ocotp_data imx8mq_ocotp_data = {
.set_timing = imx6_ocotp_set_timing,
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx8m_featctrl_data imx8mm_featctrl_data = {
@@ -948,6 +1004,7 @@ static struct imx_ocotp_data imx8mm_ocotp_data = {
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
.feat = &imx8mm_featctrl_data,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx8m_featctrl_data imx8mn_featctrl_data = {
@@ -965,6 +1022,7 @@ static struct imx_ocotp_data imx8mn_ocotp_data = {
.fuse_blow = imx6_fuse_blow_addr,
.fuse_read = imx6_fuse_read_addr,
.feat = &imx8mn_featctrl_data,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx7d_ocotp_data = {
@@ -976,6 +1034,7 @@ static struct imx_ocotp_data imx7d_ocotp_data = {
.set_timing = imx7_ocotp_set_timing,
.fuse_blow = imx7_fuse_blow_addr,
.fuse_read = imx7_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static __maybe_unused struct of_device_id imx_ocotp_dt_ids[] = {
---
base-commit: c324e9ea7f8808ae069cd8bd0aa53021c1239bd2
change-id: 20240116-feature-imx8mp-octop-driver-63d2ec54a92d
Best regards,
--
Stefan Kerkmann <s.kerkmann@pengutronix.de>
next reply other threads:[~2024-01-16 15:02 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-01-16 15:00 Stefan Kerkmann [this message]
2024-01-19 7:16 ` Sascha Hauer
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=20240116-feature-imx8mp-octop-driver-v1-1-5143a215f657@pengutronix.de \
--to=s.kerkmann@pengutronix.de \
--cc=barebox@lists.infradead.org \
--cc=s.hauer@pengutronix.de \
/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