diff mbox

[3/4] nvmem: add a driver for the Amlogic Meson6/Meson8/Meson8b SoCs

Message ID 20171001125700.1520-4-martin.blumenstingl@googlemail.com (mailing list archive)
State Superseded
Headers show

Commit Message

Martin Blumenstingl Oct. 1, 2017, 12:56 p.m. UTC
This adds a driver to access the efuse on Amlogic Meson6, Meson8 and
Meson8b SoCs.
These SoCs are accessing the efuse IP block directly through the
registers in the "secbus" region. This makes it different from the Meson
GX efuse driver which uses the "secure monitor" firmware to access the
efuse.

The efuse on Meson6 can only read one byte at a time, while the efuse on
Meson8 and Meson8b always reads 4 bytes at a time. The new driver
supports both, but due to lack of hardware Meson6 support was not tested.

Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
 drivers/nvmem/Kconfig          |  10 ++
 drivers/nvmem/Makefile         |   2 +
 drivers/nvmem/meson-mx-efuse.c | 272 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 284 insertions(+)
 create mode 100644 drivers/nvmem/meson-mx-efuse.c

Comments

Srinivas Kandagatla Oct. 2, 2017, 3:28 p.m. UTC | #1
Minor comments!!

On 01/10/17 13:56, Martin Blumenstingl wrote:
> This adds a driver to access the efuse on Amlogic Meson6, Meson8 and
> Meson8b SoCs.
> These SoCs are accessing the efuse IP block directly through the
> registers in the "secbus" region. This makes it different from the Meson
> GX efuse driver which uses the "secure monitor" firmware to access the
> efuse.
> 
> The efuse on Meson6 can only read one byte at a time, while the efuse on
> Meson8 and Meson8b always reads 4 bytes at a time. The new driver
> supports both, but due to lack of hardware Meson6 support was not tested.
> 
> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> ---
>   drivers/nvmem/Kconfig          |  10 ++
>   drivers/nvmem/Makefile         |   2 +
>   drivers/nvmem/meson-mx-efuse.c | 272 +++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 284 insertions(+)
>   create mode 100644 drivers/nvmem/meson-mx-efuse.c
> 
> diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
> +	
> +
> +static void meson_mx_efuse_mask_bits(struct meson_mx_efuse *efuse, u32 reg,
> +				     u32 mask, u32 set)
> +{
> +	u32 data;
> +
> +	data = readl(efuse->base + reg);
> +	data &= ~mask;
> +	data |= (set & mask);
> +
> +	writel(data, efuse->base + reg);
> +}
> +
> +static int meson_mx_efuse_hw_enable(struct meson_mx_efuse *efuse)
> +{
> +	int err;
> +
> +	err = clk_prepare_enable(efuse->core_clk);
> +	if (err)
> +		return err;
> +
> +	/* power up the efuse */
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_PD_ENABLE, 0);
> +
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL4,
> +				 MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE, 0);
> +
> +	return 0;
> +}
> +
> +static void meson_mx_efuse_hw_disable(struct meson_mx_efuse *efuse)
> +{
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_PD_ENABLE,
> +				 MESON_MX_EFUSE_CNTL1_PD_ENABLE);
> +
> +	clk_disable_unprepare(efuse->core_clk);
> +}
> +
> +static int meson_mx_efuse_read_addr(struct meson_mx_efuse *efuse,
> +				    unsigned int addr, u32 *value)
> +{
> +	int err;
> +	u32 regval;
> +
> +	/* write the address to read */
> +	regval = FIELD_PREP(MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, addr);
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, regval);
> +
> +	/* inform the hardware that we changed the address */
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET,
> +				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET);
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, 0);
> +
> +	/* start the read process */
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START);
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START, 0);
> +
> +	/*
> +	 * perform a dummy read to ensure that the HW has the RD_BUSY bit set
> +	 * when polling for the status below.
> +	 */
> +	readl(efuse->base + MESON_MX_EFUSE_CNTL1);
> +
> +	err = readl_poll_timeout_atomic(efuse->base + MESON_MX_EFUSE_CNTL1,
> +			regval,
> +			(!(regval & MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY)),
> +			1, 1000);
> +	if (err) {
> +		dev_err(efuse->config.dev,
> +			"Timeout while reading efuse address %u\n", addr);
> +		return err;
> +	}
> +
> +	*value = readl(efuse->base + MESON_MX_EFUSE_CNTL2);
> +
> +	return 0;
> +}
> +
> +static int meson_mx_efuse_read(void *context, unsigned int offset,
> +			       void *buf, size_t bytes)
> +{
> +	struct meson_mx_efuse *efuse = context;
> +	u32 tmp;
> +	int err, i, addr;
> +
> +	err = meson_mx_efuse_hw_enable(efuse);
> +	if (err)
> +		return err;
> +
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
> +
> +	for (i = offset; i < offset + bytes; i += efuse->config.word_size) {
> +		addr = i / efuse->config.word_size;
> +
> +		err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
> +		if (err)
> +			break;
> +
> +		memcpy(buf + i, &tmp, efuse->config.word_size);
> +	}
> +
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, 0);
> +
> +	meson_mx_efuse_hw_disable(efuse);
> +
> +	return err;
> +}
...
> +
> +static int meson_mx_efuse_probe(struct platform_device *pdev)
> +{
> +	const struct meson_mx_efuse_platform_data *drvdata;
> +	struct meson_mx_efuse *efuse;
> +	struct resource *res;
> +

...

> +
> +	efuse->core_clk = devm_clk_get(&pdev->dev, "core");
> +	if (IS_ERR(efuse->core_clk)) {
> +		dev_err(&pdev->dev, "Failed to get core clock\n");
> +		return PTR_ERR(efuse->core_clk);
> +	}
> +
> +	efuse->nvmem = nvmem_register(&efuse->config);
> +	if (IS_ERR(efuse->nvmem)) {
> +		clk_unprepare(efuse->core_clk);

Do you need this???

> +		return PTR_ERR(efuse->nvmem);
> +	}
> +
> +	platform_set_drvdata(pdev, efuse);
> +
> +	return 0;
> +}
> +
> +static int meson_mx_efuse_remove(struct platform_device *pdev)
> +{
> +	struct meson_mx_efuse *efuse = platform_get_drvdata(pdev);
> +	int err;
> +
> +	err = nvmem_unregister(efuse->nvmem);
> +
> +	clk_unprepare(efuse->core_clk);

Do you need this disable here? AFAIU, this driver would never leave the 
clk prepared unless we are in the middle of read.

> +
> +	return err;
> +}
> +
Martin Blumenstingl Oct. 2, 2017, 6:38 p.m. UTC | #2
On Mon, Oct 2, 2017 at 5:28 PM, Srinivas Kandagatla
<srinivas.kandagatla@linaro.org> wrote:
> Minor comments!!
that was a VERY quick code review (and by this I mean the time between
sending the patch and getting valuable feedback) - many thanks for
this!

>
> On 01/10/17 13:56, Martin Blumenstingl wrote:
>>
>> This adds a driver to access the efuse on Amlogic Meson6, Meson8 and
>> Meson8b SoCs.
>> These SoCs are accessing the efuse IP block directly through the
>> registers in the "secbus" region. This makes it different from the Meson
>> GX efuse driver which uses the "secure monitor" firmware to access the
>> efuse.
>>
>> The efuse on Meson6 can only read one byte at a time, while the efuse on
>> Meson8 and Meson8b always reads 4 bytes at a time. The new driver
>> supports both, but due to lack of hardware Meson6 support was not tested.
>>
>> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
>> ---
>>   drivers/nvmem/Kconfig          |  10 ++
>>   drivers/nvmem/Makefile         |   2 +
>>   drivers/nvmem/meson-mx-efuse.c | 272
>> +++++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 284 insertions(+)
>>   create mode 100644 drivers/nvmem/meson-mx-efuse.c
>>
>> diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
>> +
>> +
>> +static void meson_mx_efuse_mask_bits(struct meson_mx_efuse *efuse, u32
>> reg,
>> +                                    u32 mask, u32 set)
>> +{
>> +       u32 data;
>> +
>> +       data = readl(efuse->base + reg);
>> +       data &= ~mask;
>> +       data |= (set & mask);
>> +
>> +       writel(data, efuse->base + reg);
>> +}
>> +
>> +static int meson_mx_efuse_hw_enable(struct meson_mx_efuse *efuse)
>> +{
>> +       int err;
>> +
>> +       err = clk_prepare_enable(efuse->core_clk);
>> +       if (err)
>> +               return err;
>> +
>> +       /* power up the efuse */
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_PD_ENABLE, 0);
>> +
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL4,
>> +                                MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE, 0);
>> +
>> +       return 0;
>> +}
>> +
>> +static void meson_mx_efuse_hw_disable(struct meson_mx_efuse *efuse)
>> +{
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_PD_ENABLE,
>> +                                MESON_MX_EFUSE_CNTL1_PD_ENABLE);
>> +
>> +       clk_disable_unprepare(efuse->core_clk);
>> +}
>> +
>> +static int meson_mx_efuse_read_addr(struct meson_mx_efuse *efuse,
>> +                                   unsigned int addr, u32 *value)
>> +{
>> +       int err;
>> +       u32 regval;
>> +
>> +       /* write the address to read */
>> +       regval = FIELD_PREP(MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, addr);
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK,
>> regval);
>> +
>> +       /* inform the hardware that we changed the address */
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET,
>> +                                MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET);
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, 0);
>> +
>> +       /* start the read process */
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_START,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_START);
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_START, 0);
>> +
>> +       /*
>> +        * perform a dummy read to ensure that the HW has the RD_BUSY bit
>> set
>> +        * when polling for the status below.
>> +        */
>> +       readl(efuse->base + MESON_MX_EFUSE_CNTL1);
>> +
>> +       err = readl_poll_timeout_atomic(efuse->base +
>> MESON_MX_EFUSE_CNTL1,
>> +                       regval,
>> +                       (!(regval & MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY)),
>> +                       1, 1000);
>> +       if (err) {
>> +               dev_err(efuse->config.dev,
>> +                       "Timeout while reading efuse address %u\n", addr);
>> +               return err;
>> +       }
>> +
>> +       *value = readl(efuse->base + MESON_MX_EFUSE_CNTL2);
>> +
>> +       return 0;
>> +}
>> +
>> +static int meson_mx_efuse_read(void *context, unsigned int offset,
>> +                              void *buf, size_t bytes)
>> +{
>> +       struct meson_mx_efuse *efuse = context;
>> +       u32 tmp;
>> +       int err, i, addr;
>> +
>> +       err = meson_mx_efuse_hw_enable(efuse);
>> +       if (err)
>> +               return err;
>> +
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
>> +
>> +       for (i = offset; i < offset + bytes; i += efuse->config.word_size)
>> {
>> +               addr = i / efuse->config.word_size;
>> +
>> +               err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
>> +               if (err)
>> +                       break;
>> +
>> +               memcpy(buf + i, &tmp, efuse->config.word_size);
>> +       }
>> +
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, 0);
>> +
>> +       meson_mx_efuse_hw_disable(efuse);
>> +
>> +       return err;
>> +}
>
> ...
>>
>> +
>> +static int meson_mx_efuse_probe(struct platform_device *pdev)
>> +{
>> +       const struct meson_mx_efuse_platform_data *drvdata;
>> +       struct meson_mx_efuse *efuse;
>> +       struct resource *res;
>> +
>
>
> ...
>
>> +
>> +       efuse->core_clk = devm_clk_get(&pdev->dev, "core");
>> +       if (IS_ERR(efuse->core_clk)) {
>> +               dev_err(&pdev->dev, "Failed to get core clock\n");
>> +               return PTR_ERR(efuse->core_clk);
>> +       }
>> +
>> +       efuse->nvmem = nvmem_register(&efuse->config);
>> +       if (IS_ERR(efuse->nvmem)) {
>> +               clk_unprepare(efuse->core_clk);
>
>
> Do you need this???
oh, this is a left-over from an earlier version where I only enabled
the clock in .probe (and disabled it again in .remove)
however, this could break during suspend and it keeps the clock
unnecessarily enabled when it's not needed

>
>> +               return PTR_ERR(efuse->nvmem);
>> +       }
>> +
>> +       platform_set_drvdata(pdev, efuse);
>> +
>> +       return 0;
>> +}
>> +
>> +static int meson_mx_efuse_remove(struct platform_device *pdev)
>> +{
>> +       struct meson_mx_efuse *efuse = platform_get_drvdata(pdev);
>> +       int err;
>> +
>> +       err = nvmem_unregister(efuse->nvmem);
>> +
>> +       clk_unprepare(efuse->core_clk);
>
>
> Do you need this disable here? AFAIU, this driver would never leave the clk
> prepared unless we are in the middle of read.
yes, good catch here as well - this clk_unprepare call has to be removed

>
>> +
>> +       return err;
>> +}
>> +
Chris Moore Oct. 3, 2017, 4:48 a.m. UTC | #3
Hi,

Sorry in advance if I am being extremely stupid here.

Le 01/10/2017 à 14:56, Martin Blumenstingl a écrit :

[snip]

> +static int meson_mx_efuse_read(void *context, unsigned int offset,
> +			       void *buf, size_t bytes)
> +{
> +	struct meson_mx_efuse *efuse = context;
> +	u32 tmp;
> +	int err, i, addr;
> +
> +	err = meson_mx_efuse_hw_enable(efuse);
> +	if (err)
> +		return err;
> +
> +	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
> +				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
> +
> +	for (i = offset; i < offset + bytes; i += efuse->config.word_size) {
> +		addr = i / efuse->config.word_size;
> +
> +		err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
> +		if (err)
> +			break;
> +
> +		memcpy(buf + i, &tmp, efuse->config.word_size);
> +	}

This will not give the expected result if offset is not a multiple of 
efuse->config.word_size.
This will cause buffer overflow if bytes is not a multiple of 
efuse->config.word_size.
Shouldn't there at least be some sort of test on offset and bytes?

Also this doesn't look endian-safe, but maybe it is.

Old-timer remarks:
- I try to avoid divisions where possible but I suppose they are very 
low-cost on recent hardware.
- I don't like arithmetic on void pointers but I know it is supported by 
gcc (and possibly by recent C standards too).

Sorry again for the noise if I am being stupid.

Cheers,
Chris
Martin Blumenstingl Oct. 3, 2017, 11:14 a.m. UTC | #4
Hi Chris,

On Tue, Oct 3, 2017 at 6:48 AM, Chris Moore <moore@free.fr> wrote:
> Hi,
>
> Sorry in advance if I am being extremely stupid here.
>
> Le 01/10/2017 à 14:56, Martin Blumenstingl a écrit :
>
> [snip]
>
>> +static int meson_mx_efuse_read(void *context, unsigned int offset,
>> +                              void *buf, size_t bytes)
>> +{
>> +       struct meson_mx_efuse *efuse = context;
>> +       u32 tmp;
>> +       int err, i, addr;
>> +
>> +       err = meson_mx_efuse_hw_enable(efuse);
>> +       if (err)
>> +               return err;
>> +
>> +       meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
>> +                                MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
>> +
>> +       for (i = offset; i < offset + bytes; i += efuse->config.word_size)
>> {
>> +               addr = i / efuse->config.word_size;
>> +
>> +               err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
>> +               if (err)
>> +                       break;
>> +
>> +               memcpy(buf + i, &tmp, efuse->config.word_size);
>> +       }
>
>
> This will not give the expected result if offset is not a multiple of
> efuse->config.word_size.
I had a look at mtk-efuse.c and bcm-ocotp.c and both are doing similar
calculations
thus I assumed that the nvmem core takes care of handling this

> This will cause buffer overflow if bytes is not a multiple of
> efuse->config.word_size.
that would be very nasty indeed

> Shouldn't there at least be some sort of test on offset and bytes?
in the driver or in core? if possible these checks should be part of
the core nvmem code to prevent code duplication across various drivers
(and to prevent bugs if a specific driver forgets these checks)

> Also this doesn't look endian-safe, but maybe it is.
dumping the whole efuse via sysfs showed the same results as (the
vendor command in Amlogic's) u-boot.

> Old-timer remarks:
> - I try to avoid divisions where possible but I suppose they are very
> low-cost on recent hardware.
> - I don't like arithmetic on void pointers but I know it is supported by gcc
> (and possibly by recent C standards too).
>
> Sorry again for the noise if I am being stupid.
your questions are reasonable
this is my first nvmem driver, so there may be mistakes in the code -
maybe Srinivas could also comment on your feedback

also thank you for taking the time to review this!


Regards,
Martin
Srinivas Kandagatla Oct. 9, 2017, 10:36 a.m. UTC | #5
On 03/10/17 05:48, Chris Moore wrote:
> Hi,
> 
> Sorry in advance if I am being extremely stupid here.
> 
> Le 01/10/2017 à 14:56, Martin Blumenstingl a écrit :
> 
> [snip]
> 
>> +static int meson_mx_efuse_read(void *context, unsigned int offset,
>> +                   void *buf, size_t bytes)
>> +{
>> +    struct meson_mx_efuse *efuse = context;
>> +    u32 tmp;
>> +    int err, i, addr;
>> +
>> +    err = meson_mx_efuse_hw_enable(efuse);
>> +    if (err)
>> +        return err;
>> +
>> +    meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
>> +                 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
>> +                 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
>> +
>> +    for (i = offset; i < offset + bytes; i += efuse->config.word_size) {
>> +        addr = i / efuse->config.word_size;
>> +
>> +        err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
>> +        if (err)
>> +            break;
>> +
>> +        memcpy(buf + i, &tmp, efuse->config.word_size);
>> +    }
> 
> This will not give the expected result if offset is not a multiple of 
> efuse->config.word_size.

Offset should be always multiple of stride which is enforced in nvmem core.

nvmem_cell would return an -EINVAL if that is not the case.


--srini

> This will cause buffer overflow if bytes is not a multiple of 
> efuse->config.word_size.
> Shouldn't there at least be some sort of test on offset and bytes?
> 
> Also this doesn't look endian-safe, but maybe it is.
> 
> Old-timer remarks:
> - I try to avoid divisions where possible but I suppose they are very 
> low-cost on recent hardware.
> - I don't like arithmetic on void pointers but I know it is supported by 
> gcc (and possibly by recent C standards too).
> 
> Sorry again for the noise if I am being stupid.
> 
> Cheers,
> Chris
>
diff mbox

Patch

diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index a21a781f587d..11f1313a03de 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -144,4 +144,14 @@  config MESON_EFUSE
 	  This driver can also be built as a module. If so, the module
 	  will be called nvmem_meson_efuse.
 
+config MESON_MX_EFUSE
+	tristate "Amlogic Meson6/Meson8/Meson8b eFuse Support"
+	depends on ARCH_MESON || COMPILE_TEST
+	help
+	  This is a driver to retrieve specific values from the eFuse found on
+	  the Amlogic Meson6, Meson8 and Meson8b SoCs.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nvmem_meson_mx_efuse.
+
 endif
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 173140658693..1c2edf869f26 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -30,3 +30,5 @@  obj-$(CONFIG_NVMEM_VF610_OCOTP)	+= nvmem-vf610-ocotp.o
 nvmem-vf610-ocotp-y		:= vf610-ocotp.o
 obj-$(CONFIG_MESON_EFUSE)	+= nvmem_meson_efuse.o
 nvmem_meson_efuse-y		:= meson-efuse.o
+obj-$(CONFIG_MESON_MX_EFUSE)	+= nvmem_meson_mx_efuse.o
+nvmem_meson_mx_efuse-y		:= meson-mx-efuse.o
diff --git a/drivers/nvmem/meson-mx-efuse.c b/drivers/nvmem/meson-mx-efuse.c
new file mode 100644
index 000000000000..b7c75f3eadce
--- /dev/null
+++ b/drivers/nvmem/meson-mx-efuse.c
@@ -0,0 +1,272 @@ 
+/*
+ * Amlogic Meson6, Meson8 and Meson8b eFuse Driver
+ *
+ * Copyright (c) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#define MESON_MX_EFUSE_CNTL1					0x04
+#define MESON_MX_EFUSE_CNTL1_PD_ENABLE				BIT(27)
+#define MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY			BIT(26)
+#define MESON_MX_EFUSE_CNTL1_AUTO_RD_START			BIT(25)
+#define MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE			BIT(24)
+#define MESON_MX_EFUSE_CNTL1_BYTE_WR_DATA			GENMASK(23, 16)
+#define MESON_MX_EFUSE_CNTL1_AUTO_WR_BUSY			BIT(14)
+#define MESON_MX_EFUSE_CNTL1_AUTO_WR_START			BIT(13)
+#define MESON_MX_EFUSE_CNTL1_AUTO_WR_ENABLE			BIT(12)
+#define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET			BIT(11)
+#define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK			GENMASK(10, 0)
+
+#define MESON_MX_EFUSE_CNTL2					0x08
+
+#define MESON_MX_EFUSE_CNTL4					0x10
+#define MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE			BIT(10)
+
+struct meson_mx_efuse_platform_data {
+	const char *name;
+	unsigned int word_size;
+};
+
+struct meson_mx_efuse {
+	void __iomem *base;
+	struct clk *core_clk;
+	struct nvmem_device *nvmem;
+	struct nvmem_config config;
+};
+
+static void meson_mx_efuse_mask_bits(struct meson_mx_efuse *efuse, u32 reg,
+				     u32 mask, u32 set)
+{
+	u32 data;
+
+	data = readl(efuse->base + reg);
+	data &= ~mask;
+	data |= (set & mask);
+
+	writel(data, efuse->base + reg);
+}
+
+static int meson_mx_efuse_hw_enable(struct meson_mx_efuse *efuse)
+{
+	int err;
+
+	err = clk_prepare_enable(efuse->core_clk);
+	if (err)
+		return err;
+
+	/* power up the efuse */
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_PD_ENABLE, 0);
+
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL4,
+				 MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE, 0);
+
+	return 0;
+}
+
+static void meson_mx_efuse_hw_disable(struct meson_mx_efuse *efuse)
+{
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_PD_ENABLE,
+				 MESON_MX_EFUSE_CNTL1_PD_ENABLE);
+
+	clk_disable_unprepare(efuse->core_clk);
+}
+
+static int meson_mx_efuse_read_addr(struct meson_mx_efuse *efuse,
+				    unsigned int addr, u32 *value)
+{
+	int err;
+	u32 regval;
+
+	/* write the address to read */
+	regval = FIELD_PREP(MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, addr);
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, regval);
+
+	/* inform the hardware that we changed the address */
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET,
+				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET);
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, 0);
+
+	/* start the read process */
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START,
+				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START);
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_AUTO_RD_START, 0);
+
+	/*
+	 * perform a dummy read to ensure that the HW has the RD_BUSY bit set
+	 * when polling for the status below.
+	 */
+	readl(efuse->base + MESON_MX_EFUSE_CNTL1);
+
+	err = readl_poll_timeout_atomic(efuse->base + MESON_MX_EFUSE_CNTL1,
+			regval,
+			(!(regval & MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY)),
+			1, 1000);
+	if (err) {
+		dev_err(efuse->config.dev,
+			"Timeout while reading efuse address %u\n", addr);
+		return err;
+	}
+
+	*value = readl(efuse->base + MESON_MX_EFUSE_CNTL2);
+
+	return 0;
+}
+
+static int meson_mx_efuse_read(void *context, unsigned int offset,
+			       void *buf, size_t bytes)
+{
+	struct meson_mx_efuse *efuse = context;
+	u32 tmp;
+	int err, i, addr;
+
+	err = meson_mx_efuse_hw_enable(efuse);
+	if (err)
+		return err;
+
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE,
+				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE);
+
+	for (i = offset; i < offset + bytes; i += efuse->config.word_size) {
+		addr = i / efuse->config.word_size;
+
+		err = meson_mx_efuse_read_addr(efuse, addr, &tmp);
+		if (err)
+			break;
+
+		memcpy(buf + i, &tmp, efuse->config.word_size);
+	}
+
+	meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1,
+				 MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, 0);
+
+	meson_mx_efuse_hw_disable(efuse);
+
+	return err;
+}
+
+static const struct meson_mx_efuse_platform_data meson6_efuse_data = {
+	.name = "meson6-efuse",
+	.word_size = 1,
+};
+
+static const struct meson_mx_efuse_platform_data meson8_efuse_data = {
+	.name = "meson8-efuse",
+	.word_size = 4,
+};
+
+static const struct meson_mx_efuse_platform_data meson8b_efuse_data = {
+	.name = "meson8b-efuse",
+	.word_size = 4,
+};
+
+static const struct of_device_id meson_mx_efuse_match[] = {
+	{ .compatible = "amlogic,meson6-efuse", .data = &meson6_efuse_data },
+	{ .compatible = "amlogic,meson8-efuse", .data = &meson8_efuse_data },
+	{ .compatible = "amlogic,meson8b-efuse", .data = &meson8b_efuse_data },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, meson_mx_efuse_match);
+
+static int meson_mx_efuse_probe(struct platform_device *pdev)
+{
+	const struct meson_mx_efuse_platform_data *drvdata;
+	struct meson_mx_efuse *efuse;
+	struct resource *res;
+
+	drvdata = of_device_get_match_data(&pdev->dev);
+	if (!drvdata)
+		return -EINVAL;
+
+	efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL);
+	if (!efuse)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	efuse->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(efuse->base))
+		return PTR_ERR(efuse->base);
+
+	efuse->config.name = devm_kstrdup(&pdev->dev, drvdata->name,
+					  GFP_KERNEL);
+	efuse->config.owner = THIS_MODULE;
+	efuse->config.dev = &pdev->dev;
+	efuse->config.priv = efuse;
+	efuse->config.stride = drvdata->word_size;
+	efuse->config.word_size = drvdata->word_size;
+	efuse->config.size = SZ_512;
+	efuse->config.read_only = true;
+	efuse->config.reg_read = meson_mx_efuse_read;
+
+	efuse->core_clk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(efuse->core_clk)) {
+		dev_err(&pdev->dev, "Failed to get core clock\n");
+		return PTR_ERR(efuse->core_clk);
+	}
+
+	efuse->nvmem = nvmem_register(&efuse->config);
+	if (IS_ERR(efuse->nvmem)) {
+		clk_unprepare(efuse->core_clk);
+		return PTR_ERR(efuse->nvmem);
+	}
+
+	platform_set_drvdata(pdev, efuse);
+
+	return 0;
+}
+
+static int meson_mx_efuse_remove(struct platform_device *pdev)
+{
+	struct meson_mx_efuse *efuse = platform_get_drvdata(pdev);
+	int err;
+
+	err = nvmem_unregister(efuse->nvmem);
+
+	clk_unprepare(efuse->core_clk);
+
+	return err;
+}
+
+static struct platform_driver meson_mx_efuse_driver = {
+	.probe = meson_mx_efuse_probe,
+	.remove = meson_mx_efuse_remove,
+	.driver = {
+		.name = "meson-mx-efuse",
+		.of_match_table = meson_mx_efuse_match,
+	},
+};
+
+module_platform_driver(meson_mx_efuse_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Amlogic Meson MX eFuse NVMEM driver");
+MODULE_LICENSE("GPL v2");