2022-08-14 17:58:47

by Samuel Holland

[permalink] [raw]
Subject: [PATCH 0/4] nvmem: Support non-stride-aligned NVMEM cell data

The first half of this series fixes a bug in the sunxi SID driver,
emphasizing that it really does have a hardware-level stride of 4 bytes.

The remainder of the series tries to answer the question:

How can I use nvmem_cell_read_u8() to read byte 0x2a of a NVMEM
device that has .stride == 4?

The NVMEM cell may be at a different offset in future SoCs, so I think
it would be wrong to use nvmem_cell_read_u32() and extract the single
relevant byte in the consumer driver.

I can think of three solutions:
1) Change the NVMEM provider driver to use .stride == 1, and fix the
alignment inside that driver. Some other NVMEM implementations have
taken this path. This is not ideal because it requires allocating
an extra bounce buffer inside the driver.
2) Extend nvmem_shift_read_buffer_in_place() to handle larger bit
offsets. Specify a stride-aligned "reg" in the devicetree, and use
"bits" to provide the sub-stride offset. This adds a minimal amount
of new code, and is generic across all drivers.
3) Do the same as #2, but also remove the alignment checks from
nvmem_cell_info_to_nvmem_cell_entry_nodup() and have it convert
non-stride-aligned "reg" properties to the equivalent bit_offset
and nbits fields (and use that from nvmem_add_cells_from_of()).

Since option #3 has larger impacts on the NVMEM core, and is backward-
compatible with option #2, I have implemented option #2 in this series.


Samuel Holland (4):
nvmem: sunxi_sid: Always use 32-bit MMIO reads
nvmem: sunxi_sid: Drop the workaround on A64
dt-bindings: nvmem: Allow bit offsets greater than a byte
nvmem: core: Support reading cells with >= 8 bit offsets

.../devicetree/bindings/nvmem/nvmem.yaml | 2 +-
drivers/nvmem/core.c | 43 +++++++++++--------
drivers/nvmem/sunxi_sid.c | 23 ++++++----
3 files changed, 40 insertions(+), 28 deletions(-)

--
2.35.1


2022-08-14 18:00:51

by Samuel Holland

[permalink] [raw]
Subject: [PATCH 2/4] nvmem: sunxi_sid: Drop the workaround on A64

Now that the SRAM readout code is fixed by using 32-bit accesses, it
always returns the same values as register readout, so the A64 variant
no longer needs the workaround. This makes the D1 variant structure
redundant, so remove it.

Signed-off-by: Samuel Holland <[email protected]>
---

drivers/nvmem/sunxi_sid.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c
index 92dfe4cb10e3..a970f1741cc6 100644
--- a/drivers/nvmem/sunxi_sid.c
+++ b/drivers/nvmem/sunxi_sid.c
@@ -197,15 +197,9 @@ static const struct sunxi_sid_cfg sun8i_h3_cfg = {
.need_register_readout = true,
};

-static const struct sunxi_sid_cfg sun20i_d1_cfg = {
- .value_offset = 0x200,
- .size = 0x100,
-};
-
static const struct sunxi_sid_cfg sun50i_a64_cfg = {
.value_offset = 0x200,
.size = 0x100,
- .need_register_readout = true,
};

static const struct sunxi_sid_cfg sun50i_h6_cfg = {
@@ -218,7 +212,7 @@ static const struct of_device_id sunxi_sid_of_match[] = {
{ .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg },
{ .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg },
{ .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg },
- { .compatible = "allwinner,sun20i-d1-sid", .data = &sun20i_d1_cfg },
+ { .compatible = "allwinner,sun20i-d1-sid", .data = &sun50i_a64_cfg },
{ .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg },
{ .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg },
{ .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg },
--
2.35.1

2022-08-14 18:25:03

by Samuel Holland

[permalink] [raw]
Subject: [PATCH 4/4] nvmem: core: Support reading cells with >= 8 bit offsets

For NVMEM devices with .stride > 1, some cell values may not be aligned
to the device's stride. In this case, it is necessary to use bit_offset
to access the cell. For example, to access the third byte of an NVMEM
device with .stride == 4, we need "bits = <16 8>;" in the devicetree.

Implement this on the read side. The write side implementation would be
more complicated, and it is not necessary for read-only NVMEM devices.
For now, reject writes for these cells to avoid any incorrect behavior.

Signed-off-by: Samuel Holland <[email protected]>
---

drivers/nvmem/core.c | 43 ++++++++++++++++++++++++-------------------
1 file changed, 24 insertions(+), 19 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 1e3c754efd0d..309beba8c9f0 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1373,63 +1373,67 @@ void nvmem_cell_put(struct nvmem_cell *cell)
}
EXPORT_SYMBOL_GPL(nvmem_cell_put);

-static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
+static int nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
{
+ int bit_offset = cell->bit_offset, bytes, i;
u8 *p, *b;
- int i, extra, bit_offset = cell->bit_offset;

p = b = buf;
if (bit_offset) {
+ int byte_offset = bit_offset / BITS_PER_BYTE;
+
+ b += byte_offset;
+ bit_offset %= BITS_PER_BYTE;
+ bytes = cell->bytes - byte_offset;
+
/* First shift */
- *b++ >>= bit_offset;
+ *p = *b++ >> bit_offset;

/* setup rest of the bytes if any */
- for (i = 1; i < cell->bytes; i++) {
+ for (i = 1; i < bytes; i++) {
/* Get bits from next byte and shift them towards msb */
- *p |= *b << (BITS_PER_BYTE - bit_offset);
-
- p = b;
- *b++ >>= bit_offset;
+ *p++ |= *b << (BITS_PER_BYTE - bit_offset);
+ *p = *b++ >> bit_offset;
}
- } else {
- /* point to the msb */
- p += cell->bytes - 1;
}

/* result fits in less bytes */
- extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
- while (--extra >= 0)
- *p-- = 0;
+ bytes = DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
+ p = buf + bytes;
+ memset(p, 0, cell->bytes - bytes);

/* clear msb bits if any leftover in the last byte */
if (cell->nbits % BITS_PER_BYTE)
- *p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);
+ p[-1] &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);
+
+ return bytes;
}

static int __nvmem_cell_read(struct nvmem_device *nvmem,
struct nvmem_cell_entry *cell,
void *buf, size_t *len, const char *id)
{
+ int bytes = cell->bytes;
int rc;

- rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes);
+ rc = nvmem_reg_read(nvmem, cell->offset, buf, bytes);

if (rc)
return rc;

/* shift bits in-place */
if (cell->bit_offset || cell->nbits)
- nvmem_shift_read_buffer_in_place(cell, buf);
+ bytes = nvmem_shift_read_buffer_in_place(cell, buf);

if (nvmem->cell_post_process) {
rc = nvmem->cell_post_process(nvmem->priv, id,
- cell->offset, buf, cell->bytes);
+ cell->offset, buf, bytes);
if (rc)
return rc;
}

if (len)
- *len = cell->bytes;
+ *len = bytes;

return 0;
}
@@ -1526,6 +1530,7 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si
int rc;

if (!nvmem || nvmem->read_only ||
+ cell->bit_offset >= BITS_PER_BYTE ||
(cell->bit_offset == 0 && len != cell->bytes))
return -EINVAL;

--
2.35.1

2022-08-15 09:02:58

by Icenowy Zheng

[permalink] [raw]
Subject: Re: [PATCH 2/4] nvmem: sunxi_sid: Drop the workaround on A64

在 2022-08-14星期日的 12:36 -0500,Samuel Holland写道:
> Now that the SRAM readout code is fixed by using 32-bit accesses, it
> always returns the same values as register readout, so the A64
> variant
> no longer needs the workaround. This makes the D1 variant structure
> redundant, so remove it.

Is this really tested on A64?

>
> Signed-off-by: Samuel Holland <[email protected]>
> ---
>
>  drivers/nvmem/sunxi_sid.c | 8 +-------
>  1 file changed, 1 insertion(+), 7 deletions(-)
>
> diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c
> index 92dfe4cb10e3..a970f1741cc6 100644
> --- a/drivers/nvmem/sunxi_sid.c
> +++ b/drivers/nvmem/sunxi_sid.c
> @@ -197,15 +197,9 @@ static const struct sunxi_sid_cfg sun8i_h3_cfg =
> {
>         .need_register_readout = true,
>  };
>  
> -static const struct sunxi_sid_cfg sun20i_d1_cfg = {
> -       .value_offset = 0x200,
> -       .size = 0x100,
> -};
> -
>  static const struct sunxi_sid_cfg sun50i_a64_cfg = {
>         .value_offset = 0x200,
>         .size = 0x100,
> -       .need_register_readout = true,
>  };
>  
>  static const struct sunxi_sid_cfg sun50i_h6_cfg = {
> @@ -218,7 +212,7 @@ static const struct of_device_id
> sunxi_sid_of_match[] = {
>         { .compatible = "allwinner,sun7i-a20-sid", .data =
> &sun7i_a20_cfg },
>         { .compatible = "allwinner,sun8i-a83t-sid", .data =
> &sun50i_a64_cfg },
>         { .compatible = "allwinner,sun8i-h3-sid", .data =
> &sun8i_h3_cfg },
> -       { .compatible = "allwinner,sun20i-d1-sid", .data =
> &sun20i_d1_cfg },
> +       { .compatible = "allwinner,sun20i-d1-sid", .data =
> &sun50i_a64_cfg },
>         { .compatible = "allwinner,sun50i-a64-sid", .data =
> &sun50i_a64_cfg },
>         { .compatible = "allwinner,sun50i-h5-sid", .data =
> &sun50i_a64_cfg },
>         { .compatible = "allwinner,sun50i-h6-sid", .data =
> &sun50i_h6_cfg },


2022-08-16 03:48:14

by Samuel Holland

[permalink] [raw]
Subject: Re: [PATCH 2/4] nvmem: sunxi_sid: Drop the workaround on A64

Hi Icenowy,

On 8/15/22 3:37 AM, Icenowy Zheng wrote:
> 在 2022-08-14星期日的 12:36 -0500,Samuel Holland写道:
>> Now that the SRAM readout code is fixed by using 32-bit accesses, it
>> always returns the same values as register readout, so the A64
>> variant
>> no longer needs the workaround. This makes the D1 variant structure
>> redundant, so remove it.
>
> Is this really tested on A64?

Yes, I tested this on a Pine A64-LTS. You can see the difference easily with
md.{b,w,l,q} in the U-Boot shell.

I verified that all three of the following are identical:
- NVMEM exported to sysfs with the register readout method
- NVMEM exported to sysfs with this patch series applied
- SRAM dump made with md.l in the U-Boot shell

Regards,
Samuel